на чем пишут браузеры

Разрабатываем свой браузер с нуля. Часть первая: HTML

Продолжаем цикл статей по разработке браузерного движка.

В данной статье я расскажу как создать самый быстрый HTML-парсер c DOM. Мы рассмотрим HTML спецификацию и чем она плоха относительно производительности и потребления ресурсов при разборе HTML.

С данной темой я докладывался на прошедшем HighLoad++. Конференцию не каждый может посетить, плюс в статье больше деталей.

Я предполагаю, что читатель обладает базовыми знаниями об HTML: теги, ноды, элементы, пространство имён.

Спецификация HTML

Прежде чем начать хоть как-то затрагивать реализацию HTML-парсера необходимо понять какой HTML спецификации верить.

Существует две HTML спецификации:

UPDATE: К сожалению, приведенные ссылки на спецификации не открываются из России. Видимо, «эхо войны» с телеграмм.

Процесс парсинга HTML

Процесс построения HTML дерева можно разделить на четыре части:

Рассмотрим каждую стадию по отдельности.

Декодер

Токенизатор принимает на вход юникод символы (code points). Соответственно, нам необходимо конвертировать текущий байтовый поток в юникод символы. Для этого необходимо воспользоваться спецификацией Encoding.

Если мы имеем HTML с неизвестной кодировкой (нет HTTP заголовка) то нам необходимо её определить до начала декодирования. Для этого мы воспользуемся алгоритмом encoding sniffing algorithm.

В спецификации Encoding оговаривается минимальный набор поддерживаемых кодировок браузерным движком (всего 21): UTF-8, ISO-8859-2, ISO-8859-7, ISO-8859-8, windows-874, windows-1250, windows-1251, windows-1252, windows-1254, windows-1255, windows-1256, windows-1257, windows-1258, gb18030, Big5, ISO-2022-JP, Shift_JIS, EUC-KR, UTF-16BE, UTF-16LE и x-user-defined.

Предварительная обработка

После того как мы декодировали байты в юникод символы нам необходимо провести «зачистку». А именно, заменить все символы возврата каретки ( \r ) за которыми следует символ перевода строки ( \n ) на символ возврата каретки ( \r ). Затем, заменить все символы возврата каретки на символ перевода строки ( \n ).

Но, на самом деле так никто не делает. Делают проще:

Если попался символ возврата каретки ( \r ) то смотрим есть ли символ перевода строки ( \n ). Если есть то меняем оба символа на символ перевода строки ( \n ), если нет то меняем только первый символ ( \r ) на перевод строки ( \n ).

На этом предварительная обработка данных завершена. Да, всего-то надо избавиться от символов возврата каретки, чтобы они не попадали в токенизатор. Токенизатор не ожидает и не знает, что делать с символом возврата каретки.

Ошибки парсинга

Чтобы в дальнейшем не возникало вопросов стоит сразу рассказать, что такое ошибка парсинга ( parse error ).

На самом деле ничего страшного. Звучит грозно, но по факту это лишь предупреждение о том, что мы ожидали одно, а имеем другое.

Ошибка парсинга не остановит процесс обработки данных или построение дерева. Это сообщение которое сигнализирует, что у нас не валидный HTML.

К слову, некоторые ошибки парсинга ведут к последствиям. К примеру, если указать «плохой» то HTML дерево будет помечен как QUIRKS и изменится логика работы некоторых DOM функций.

Токенизатор

Как уже было сказано ранее, токенизатор принимает на вход юникод символы. Это конечный автомат (state machine) который имеет 80 состояний. В каждом состоянии условия для юникод символов. В зависимости от пришедшего символа токенизатор может:

Токенизатор создает токены шести видов: DOCTYPE, Start Tag, End Tag, Comment, Character, End-Of-File. Которые поступают в стадию построения дерева.

Примечательно, что токенизатор знает не о всех своих состояниях, а где о 40% (взял с потолка, для примера). «Зачем же остальные?» — спросите вы. Об остальных 60% знает стадия построения дерева.

Это сделано для того, чтобы правильно парсить такие теги как

Источник

Как создать свой собственный браузер для Windows 10 на HTML и JavaScript

За последние несколько месяцев мы внесли множество улучшений в движок рендеринга Microsoft Edge (EdgeHTML), делая особый акцент на совместимости с современными браузерами и соответствии новым и грядущим стандартам. Помимо того, что EdgeHTML лежит в основе браузера Microsoft Edge, он также доступен для приложений на Universal Windows Platform (UWP) через элемент управления WebView. Сегодня мы хотим рассказать, как можно использовать WebView для создания своего браузера в Windows 10.

Используя стандартные веб-технологии, включая JavaScript, HTML и CSS, мы создали простое UWP-приложение, которое содержит внутри WebView и реализует базовую функциональность: навигацию и работу с избранным. Подобные приемы могут быть использованы в любом UWP-приложении для прозрачной интеграции веб-контента.

В основе нашего примера лежит мощный элемент управления WebView. Помимо комплексного набора API, данный элемент также позволяет преодолеть некоторые ограничения, присущие iframe, например, отслеживание фреймов (когда некоторый сайт меняет свое поведение в случае выполнения внутри iframe) и сложность определения загрузки документа. В дополнение x-ms-webview, — так WebView задается в HTML, — дает доступ к функциональности, не доступной в iframe, в частности, улучшенный доступ к локальному контенту и возможности делать снимки содержимого. Когда вы используете элемент управления WebView, вы получаете тот же самый движок, что и в Microsoft Edge.

Создаем браузер

Как было написано выше, браузер базируется на элементе управления WebView для HTML, а для создания и оживления пользовательского интерфейса в основном используется JavaScript. Проект создан в Visual Studio 2015 и представляет собой универсальное Windows-приложение на JavaScript.

Помимо JavaScript, мы также использовали немного HTML и CSS, а также некоторое количество строк кода на C++ для поддержки комбинаций клавиш, но это не требуется в простом случае.

Также мы пользуемся новыми возможностями нового ECMAScript 2015 (ES2015), поддерживаемыми в Chakra, JavaScript-движке, работающем в Microsoft Edge и элементе управления WebView. ES2015 позволил нам сократить количество генерируемого и шаблонного кода, тем самым существенно упростив реализацию идеи. Мы использовали следующие возможности ES2015 при создании приложения: Array.from(), Array.prototype.find(), arrow functions, method properties, const, for-of, let, Map, Object.assign(), Promises, property shorthands, Proxies, spread operator, String.prototype.includes(), String.prototype.startsWith(), Symbols, template strings и Unicode code point escapes.

Читайте также:  на что тратить баллы шелл

Интерфейс пользователя

Пользовательский интерфейс включает следующие десять компонентов:

Дополнительная функциональность

Мы также реализовали несколько дополнительных возможностей, чтобы сделать работу с браузером еще более приятной:

Использование WebView

Введенный для JavaScript-приложений в Windows 8.1 элемент управления WebView, иногда также упоминаемый по имени тега x-ms-webview, позволяет хостить веб-контент внутри вашего Windows-приложения. Он доступен как для HTML, так и для XAML.Для начала работы достаточно разместить соответствующий элемент в коде страницы.

Разработка браузера

Мы будем использовать 15 различных API x-ms-webview. Все кроме двух из них управляют навигацией между страницами с некотором смысле. Давайте посмотрим, как можно использовать данные интерфейсы для создания различных элементов UI.

Управление кнопками назад и вперед

Когда вы нажимаете кнопку назад, браузер возвращает предыдущую страницу из истории браузера, если она доступна. Аналогично, когда вы нажимаете кнопку вперед, браузер возвращает последующую страницу из истории, если она также доступна. Для реализации подобной логики мы используем методы goBack() и goForward(), соответственно. Данные функции автоматически осуществят навигацию на корректную страницу из стека навигации.

После перехода на некоторую страницу, мы также обновляем текущее состояние кнопок, чтобы предотвратить «возможность» навигации, когда мы достигаем одного из концов стека навигации. Другими словами, мы отключаем кнопки навигации вперед или назад, проверяя свойства canGoBack или canGoForward на равенство false.

Управление кнопками обновления и остановки

Кнопки обновления и остановки слегка отличаются от остальных компонент панели навигации тем, что они используют одно и то же место в UI. Когда страница загружается, нажатие на кнопку остановит загрузку, спрячет «кольцо прогресса» и отобразит иконку обновления. И наоборот, когда страница загружена, нажатие на кнопку запустит обновление страницы и (в другой части кода) отобразит иконку остановки. Мы используем методы refresh() или stop() в зависимости от текущих условий.

Управление адресной строкой

В целом, реализация адресной строки может быть очень простой. Когда адрес URL введен в текстовое поле, нажатие Enter вызовет метод navigate(), используя содержимое input-элемента адресной строки в качестве параметра.

Однако современные браузеры пошли сильно дальше и внедряют дополнительную функциональность для удобства пользователей. Это добавляет некоторую сложность в реализации – и тут все зависит от сценариев, которые вы хотите поддержать.

Вот пример сценария, который мы попробовали реализовать. Допустим, в адресную строку введено значение “microsoft.com”. Адрес не является полным. Если такое значение передать в метод navigate(), он завершится неудачей. Наш браузер должен знать, что URL не полный, и уметь определить, какой корректный протокол подставить: http или https. Более того, возможно, что введенное значение и не предполагалось адресом. К примеру, мы могли ввести в адресную строку значение “seahawks”, надеясь, что, как и во многих браузерах, строка также работает как поле поиска. Браузер должен понять, что значение не является адресом, и попробовать «найти» его в поисковой системе.

Отображение favicon

Запрос favicon – нетривиальная задача, так как существует несколько способов, как икона может быть задана. Самый простой способ – это проверить корень веб-сайта на наличие файла «favicon.ico». Однако некоторые сайты могут быть на поддомене и поэтому иметь отличную иконку. К примеру, иконка на “microsoft.com” отличается от иконки на “windows.microsoft.com”. Чтобы исключить двусмысленность, можно использовать другой способ — проверить разметку страницы на наличие link-тека внутри документа с rel-атрибутом, равным “icon” или “shortcut icon”.

Мы используем метод invokeScriptAsync(), чтобы вставить внутрь элемента управления WebView скрипт, который вернет строку в случае успеха. Наш скрипт ищет внутри страницы все элементы с link-теком, проверяет, если rel-атрибут содержит слово “icon”, и в случае совпадения возвращает значение “href”-атрибута назад в приложение.

Как упомянуто выше, мы используем в нашем коде возможности из новой спецификации ES2015. Вы могли заметить использование стрелочной нотации во многих примерах выше, а также ряд других возможностей. Вставляемый скрипт – это отличный пример улучшения кода, достигаемого за счет поддержки ES2015.

Поддержка комбинаций клавиш

В отличие от возможностей, которые мы реализовали выше, поддержка комбинаций клавиш потребует от нас небольшого куска кода на C++ или C#, обернутого в виде Windows Runtime (WinRT) компонента.

Чтобы определить нажатие горячих клавиш для выполнения тех или иных действий, например, чтобы при нажатии комбинации Ctrl+L выделять адресную строку или по F11 переключаться в полноэкранный режим, нам нужно вставить еще один скрипт в WebView. Для этого мы используем метод invokeScriptAsync(), который мы уже упоминали выше. Однако, нам нужно как-то сообщать назад в слой приложения, когда те или иные клавиши нажаты.

С помощью метода addWebAllowedObject(), мы можем выставить для инжектируемого кода метод, через который можно будет передавать нажимаемые клавиши в слой приложения на JavaScript. Также важно понимать, что в Windows 10, элемент управления WebView выполняется в отдельном потоке. Нам нужно создать диспетчер, который будет передавать события в поток UI, чтобы слой приложения мог их обрабатывать.

Внешний вид браузера

Теперь, когда мы разобрались с ключевыми API WebView, давайте немного улучшим внешний вид нашего браузера.

Брендирование заголовка

Используя API Windows Runtime, мы можем поменять свойство ApplicationView.TitleBar, чтобы настроить цветовую палитру все компонентов заголовка приложения. В нашем браузере при загрузке приложения мы меняем цвета так, чтобы они соответствовали панели навигации. Мы также обновляем цвета при открытии меню, чтобы соответствовать фону меню. Каждый цвет нужно задавать как объект с RGBA свойствами. Для удобства мы создали вспомогательную функцию, генерирующую нужный формат из шестнадцатеричной строковой записи.

Читайте также:  куда можно сходить в тюмени подросткам

Прочие возможности

Индикация прогресса, а также меню настроек и избранного используют CSS transitions для анимации. Из меню настроек временные веб-данные можно очистить, используя метод clearTemporaryWebDataAsync(). А в меню избранного отображаемый список хранится в JSON-файле в корневой папке перемещаемого хранилища данных приложения.

Исходный код

Полный пример кода доступен в нашем репозитарии на GitHub. Вы можете также попробовать демонстрационный браузер, установив соответствующее приложение из Windows Store, или развернув приложение из проекта для Visual Studio.

Источник

Как работают браузеры — введение в безопасность веб-приложений

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


Chrome и lynx

Браузер — это движок рендеринга. Его работа заключается в том, чтобы загрузить веб-страницу и представить её в понятном для человека виде.

Хоть это и почти преступное упрощение, но пока это все, что нам нужно знать на данный момент.

Например, lynx — это легкий текстовый браузер, работающий из командной строки. В основе lynx лежат те же самые принципы, которые вы найдете в любых других «мейнстримных» браузерах. Пользователь вводит веб-адрес (URL), браузер скачивает документ и отображает его — единственное отличие состоит в том, что lynx использует не движок графического рендеринга, а текстовый интерфейс, благодаря которому такие сайты, как Google, выглядят так:

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

Что делает браузер?

Короче говоря, работа браузера в основном состоит из

Разрешение DNS

Этот процесс помогает браузеру узнать, к какому серверу он должен подключиться, когда пользователь вводит URL. Браузер связывается с DNS-сервером и обнаруживает, что google.com соответствует набору цифр 216.58.207.110 — IP-адресу, к которому может подключиться браузер.

HTTP-обмен

Как только браузер определит, какой сервер будет обслуживать наш запрос, он установит с ним TCP-соединение и начнет HTTP-обмен. Это не что иное, как способ общения браузера с нужным ему сервером, а для сервера — способ отвечать на запросы браузера.

HTTP — это просто название самого популярного протокола для общения в сети, и браузеры в основном выбирают HTTP при общении с серверами. HTTP-обмен подразумевает, что клиент (наш браузер) отправляет запрос, а сервер присылает ответ.

Например, после того, как браузер успешно подключится к серверу, обслуживающему google.com, он отправит запрос, который выглядит следующим образом

GET / HTTP/1.1
Host: google.com
Accept

Давайте разберем запрос построчно:

Воу, на этот раз довольно много информации, которую нужно переварить. Сервер сообщает нам, что запрос был выполнен успешно (200 OK) и добавляет к ответу несколько заголовков, из которых например, можно узнать, какой именно сервер обработал наш запрос (Server: gws), какова политика X-XSS-Protection этого ответа и так далее и тому подобное.

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

На данный момент все, что вам нужно знать — это то, что клиент и сервер обмениваются информацией и что они делают это через HTTP-протокол.

Рендеринг

Последним по счёту, но не последним по значению идет процесс рендеринга. Насколько хорош браузер, если единственное, что он покажет пользователю, это список забавных символов?

В теле ответа сервер включает представление запрашиваемого документа в соответствии с заголовком Content-Type. В нашем случае тип содержимого был установлен на text/html, поэтому мы ожидаем HTML-разметку в ответе — и именно ее мы и находим в теле документа.

Это как раз тот момент, где браузер действительно проявляет свои способности. Он считывает и анализирует HTML-код, загружает дополнительные ресурсы, включенные в разметку (например, там могут быть указаны для подгрузки JavaScript-файлы или CSS-документы) и представляет их пользователю как можно скорее.

Еще раз, конечным результатом должно стать то, что доступно для восприятия среднестатистического Васи.

Если вам нужно более детально объяснение того, что действительно происходит, когда мы нажимаем клавишу ввода в адресной строке браузера, я бы предложил прочитать статью «Что происходит, когда…», очень дотошную попытку объяснить механизмы, лежащие в основе этого процесса.

Поскольку это серия посвящена безопасности, я собираюсь дать подсказку о том, что мы только что узнали: злоумышленники легко зарабатывают на жизнь уязвимостями в части HTTP-обмена и рендеринга. Уязвимости, злонамеренные пользователи и прочие фантастические твари встречаются и в других местах, но более эффективный подход к обеспечению защиты именно на упомянутых уровнях уже позволяет вам добиваться успехов в улучшении вашего состояния безопасности.

Вендоры

4 самых популярных браузера принадлежат разным вендорам:

W3C является краеугольным камнем разработки стандартов, но браузеры нередко разрабатывают свои собственные функции, которые в конечном итоге превращаются в веб-стандарты, и безопасность тут не является исключением.

Например, в Chrome 51 были введены файлы cookie SameSite — функция, которая позволила веб-приложениям избавиться от определенного типа уязвимости, известной как CSRF (подробнее об этом позже). Другие производители решили, что это хорошая идея, и последовали ее примеру, что привело к тому, что подход SameSite стал веб-стандартом: на данный момент Safari является единственным крупным браузером без поддержки файлов cookie SameSite.

Читайте также:  на что влияет битрейт стрима в обс

Это говорит нам о двух вещах:

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

В нашем случае, если мы решим, что будем нейтрализовывать атаки CSRF только с помощью файлов cookie SameSite, мы должны знать, что мы подвергаем риску наших пользователей Safari. И наши пользователи тоже должны это знать.

И последнее, но не менее важное: вы должны помнить, что вы можете решить, поддерживать ли версию браузера или нет: поддержка каждой версии браузера будет непрактичной (вспомните хпро Internet Explorer 6). Несмотря на это, уверенная поддержка нескольких последних версий основных браузеров — как правило, хорошее решение. Однако, если вы не планируете предоставлять защиту на какой-то определенной платформе, очень желательно, чтобы ваши пользователи об этом знали.

Совет для профи: вы никогда не должны поощрять своих пользователей использовать устаревшие браузеры или активно поддерживать их. Даже если вы приняли все необходимые меры предосторожности, другие веб-разработчики этого не сделали. Поощряйте пользователей использовать последнюю поддерживаемую версию одного из основных браузеров.

Вендор или стандартный баг?

Тот факт, что обычный пользователь обращается к нашему приложению благодаря помощи стороннего клиентского программного обеспечения (браузера), добавляет еще один уровень, усложняющий путь к удобному и безопасному просмотру веб-страниц: сам браузер может быть источником уязвимости безопасности.

Вендоры, как правило, предоставляют вознаграждения (также известные как баг-баунти) исследователям безопасности, которые могут искать уязвимость в самом браузере. Эти ошибки связаны не с вашим веб-приложением, а с тем, как браузер самостоятельно управляет безопасностью.

Например, программа поощрений Chrome позволяет исследователям безопасности обращаться к команде безопасности Chrome, чтобы сообщить об обнаруженных ими уязвимостях. Если факт наличия уязвимости подтвердится, будет выпущено исправление и, как правило, опубликовано уведомление о безопасности, а исследователь получит (обычно финансовое) вознаграждение от программы.

Такие компании, как Google, инвестируют достаточно солидный капитал в свои программы Bug Bounty, поскольку это позволяет компаниям привлекать множество исследователей, обещая им финансовую выгоду в случае обнаружения ими каких-либо проблем с тестируемым программным обеспечением.

В программе Bug Bounty выигрывают все: поставщику удается повысить безопасность своего программного обеспечения, а исследователям платят за их находки. Мы обсудим эти программы позже, так как я считаю, что инициативы Bug Bounty заслуживают отдельного раздела в ландшафте аспектов безопасности.

Джейк Арчибальд (Jake Archibald) — разработчик-«адвокат» в Google, который обнаружил уязвимость, затрагивающую несколько браузеров. Он задокументировал свои усилия по ее обнаружению, процесс обращения к различным вендорам, затронутым уязвимостью, и реакцию представителей вендоров в интересном блог-посте, который я рекомендую вам прочитать.

Браузер для разработчиков

К настоящему времени мы должны были понять очень простую, но довольно важную концепцию: браузеры — это всего лишь HTTP-клиенты, созданные для «усредненного» интернет-пользователя.

Браузеры определенно более мощны, чем простой HTTP-клиент для какой-либо платформы (например, вспомните, что у NodeJS есть зависимость от ‘http’), но, в конце концов, они «просто» продукт естественной эволюции более простых HTTP-клиентов.

Что до разработчиков, нашим HTTP-клиентом, вероятно, является cURL от Daniel Stenberg, одна из самых популярных программ, которую веб-разработчики используют ежедневно. Она позволяет нам осуществлять HTTP-обмен на лету, отправляя HTTP-запрос из нашей командной строки:

В приведенном выше примере мы запросили документ по адресу localhost:8080/, и локальный сервер успешно на него ответил.

Вместо того, чтобы выгружать тело ответа в командную строку, мы использовали флаг -I, который сообщает cURL, что нас интересуют только заголовки ответа. Сделав еще шаг вперед, мы можем дать команду cURL выдавать немного больше информации, включая фактический запрос, который он выполняет, чтобы мы могли лучше изучить весь этот HTTP-обмен. Опция, которую мы должны использовать: -v (verbose, подробнее):

Примерно та же информация доступна в популярных браузерах посредством их DevTools.

Как мы уже видели, браузеры представляют собой не более чем сложные HTTP-клиенты. Конечно, они добавляют огромное количество функций (например, управление учетными данными, создание закладок, история и т. Д.), Но правда в том, что они были рождены как HTTP-клиенты для людей. Это важно, так как в большинстве случаев вам не нужен браузер для проверки безопасности вашего веб-приложения, когда вы можете просто «закурлить его» и посмотреть на ответ.

И последнее, что я хотел бы отметить: браузером может быть все, что угодно. Если у вас есть мобильное приложение, которое использует API-интерфейсы по протоколу HTTP, то такое приложение является вашим браузером — оно просто настроено вами по индивидуальному заказу, которое распознает только определенный тип HTTP-ответов (из вашего собственного API).

Погружение в протокол HTTP

Как мы уже упоминали, что собираемся наиболее подробно осветить фазы HTTP-обмена и рендеринга, поскольку именно они предоставляют наибольшее количество векторов атак для злоумышленников.

В следующей статье мы более подробно рассмотрим протокол HTTP и попытаемся понять, какие меры мы должны предпринять для обеспечения безопасности HTTP-обмена.

Источник

Строительный портал