введение
В последнее время меня больше интересует оптимизация производительности веба, а интерфейсный код в основном работает в браузере. Если у вас есть четкое представление о принципе работы браузера, вы можете указать направление и теоретическую основу для оптимизации веб-производительности.
Основная ссылка этой статьиHow Browsers Work: Behind the scenes of modern web browsers. На этой основе устроен. Лично я считаю, что это хорошая статья, поэтому я систематизирую и перепечатываю ее, надеясь помочь всем. (Статья относительно большая, это займет некоторое время ^_^)
содержание
-
Изучение принципа работы браузеров
- введение
-
содержание
- Введение
- движок рендеринга
- Парсинг и построение дерева DOM
- Построение дерева рендеринга
- макет
- рисовать
- Динамические изменения
- Потоки движка рендеринга
- Модель визуализации CSS2
- Ссылаться на
- заключительные замечания
Введение
браузер, о котором мы говорим
На сегодняшний день используется пять основных браузеров: Internet Explorer, Firefox, Safari, Chrome и Opera. В этой статье в качестве примеров используются браузеры с открытым исходным кодом, а именно Firefox, Chrome и Safari (частично с открытым исходным кодом).
Основные возможности браузера
Основная функция браузера — сделать запрос к серверу для отображения выбранного вами сетевого ресурса в окне браузера. Упомянутые здесь ресурсы обычно относятся к документам HTML, но также могут быть PDF-файлами, изображениями или другими типами. Местоположение ресурса указывается пользователем с помощью URI (унифицированного идентификатора ресурса).
То, как браузеры интерпретируют и отображают HTML-файлы, указано в спецификациях HTML и CSS. Эти спецификации поддерживаются организацией веб-стандартов W3C (Консорциум World Wide Web). На протяжении многих лет браузеры не полностью соответствовали этим спецификациям при разработке собственных уникальных расширений, что создавало серьезные проблемы совместимости для веб-разработчиков. Сегодня большинство браузеров более или менее соответствуют спецификациям.
Пользовательский интерфейс браузера имеет много общих элементов, в том числе:
- Адресная строка для ввода URI
- кнопки вперед и назад
- Опции настроек закладок
- Кнопки «Обновить» и «Стоп», чтобы обновить и остановить загрузку текущего документа.
- Кнопка «Домой» для возврата на главную страницу
Высокоуровневая структура браузера
Основными компонентами браузера являются:
- Пользовательский интерфейс — включает адресную строку, кнопки «вперед/назад», меню закладок и многое другое. За исключением запрошенной вами страницы, которая отображается в главном окне браузера, различные отображаемые части являются частью пользовательского интерфейса.
- Браузерный движок — передача инструкций между пользовательским интерфейсом и движком рендеринга.
- Механизм рендеринга — отвечает за отображение запрошенного контента. Если запрошенный контент представляет собой HTML, он отвечает за синтаксический анализ содержимого HTML и CSS и отображение проанализированного содержимого на экране.
- Сеть — для сетевых вызовов, таких как HTTP-запросы. Его интерфейс не зависит от платформы и обеспечивает базовую реализацию для всех платформ.
- Серверная часть пользовательского интерфейса — для рисования основных виджетов, таких как поля со списком и окна. Он предоставляет независимый от платформы общий интерфейс, используя под капотом методы пользовательского интерфейса операционной системы.
- Интерпретатор JavaScript. Используется для анализа и выполнения кода JavaScript.
- хранилище данных. Это слой сохранения. Браузер должен сохранять на жестком диске различные данные, например файлы cookie. Новая спецификация HTML (HTML5) определяет «веб-базу данных», полную (но облегченную) базу данных в браузере.
движок рендеринга
Механизм рендеринга в основном используется для отображения таких ресурсов, как HTML, XML, Picture, PDF. Обсуждаемый здесь браузер (Firefox, браузер Chrome и Safari) основан на двух механизмах рендеринга. Firefox использует Gecko, самодельный движок рендеринга Mozilla. Браузеры Safari и Chrome используются webkit.
WebKit — это механизм рендеринга с открытым исходным кодом, первоначально разработанный для платформы Linux, а затем модифицированный Apple для поддержки Mac и Windows.
Основной процесс
Механизм рендеринга изначально получает содержимое запрошенного документа с сетевого уровня, а размер содержимого обычно ограничен 8000 блоков.
Затем выполните основной процесс, как показано ниже:
Механизм рендеринга начнет синтаксический анализ HTML-документа и превратит каждую разметку в узел DOM в «дереве содержимого». Он также анализирует внешние файлы CSS и данные стиля в элементах стиля. Эта информация о стиле с визуальными инструкциями в HTML будет использоваться для создания другой древовидной структуры: дерева рендеринга.
Дерево рендеринга содержит несколько прямоугольников с визуальными свойствами, такими как цвет и размер. Порядок расположения прямоугольников — это порядок, в котором они будут отображаться на экране.
После того, как дерево рендеринга построено, оно переходит к процессу «макетирования», который заключается в назначении каждому узлу точной координаты, где он должен появиться на экране. Следующим этапом является отрисовка: механизм рендеринга проходит по дереву рендеринга, отрисовывая каждый узел бэкэнд-слоем пользовательского интерфейса.
Важно отметить, что это постепенный процесс. Чтобы добиться лучшего взаимодействия с пользователем, механизм рендеринга будет стремиться отображать контент на экране как можно быстрее. Не нужно ждать, пока весь HTML-документ будет проанализирован, чтобы начать построение дерева рендеринга и настройку макета. Механизм рендеринга анализирует и отображает части контента, непрерывно получая и обрабатывая остальной контент из сети.
Основной процесс выглядит следующим образом:
Как видно из рисунков 3 и 4, хотя WebKit и Gecko используют немного разную терминологию, общий процесс в основном одинаков.
Gecko называет дерево элементов визуального форматирования «деревом фреймов». Каждый элемент является рамкой. WebKit использует термин «дерево рендеринга», которое состоит из «объектов рендеринга». Для размещения элементов в WebKit используется термин «макет», а в Gecko — «перекомпоновка». WebKit использует термин «дополнение» для процесса соединения узлов DOM и визуальной информации для создания дерева рендеринга. Тонкое несемантическое отличие заключается в том, что Gecko также имеет слой между HTML и деревом DOM, называемый «слотом контента» для создания элементов DOM. Мы рассмотрим каждую часть процесса одну за другой:
Парсинг и построение дерева DOM
Анализ — Обзор
Синтаксический анализ — очень важная часть движка рендеринга, поэтому мы объясним его более подробно. Во-первых, давайте представим анализ.
Разбор документа означает преобразование его в осмысленную структуру, то есть структуру, которую можно понять и использовать в коде. Результатом синтаксического анализа обычно является дерево узлов, представляющее структуру документа, которое называется деревом синтаксического анализа или синтаксическим деревом.
Пример — синтаксический анализ выражения 2 + 3 — 1 возвращает следующее дерево:
грамматика
Синтаксический анализ основан на правилах грамматики, которым следует документ (язык или формат, в котором был написан документ). Все анализируемые форматы должны соответствовать определенной грамматике (состоящей из словаря и правил грамматики). Это называется контекстно-свободной грамматикой. Человеческие языки не входят в число таких языков и поэтому не могут быть проанализированы с помощью обычных методов синтаксического анализа.
Комбинация парсера и лексера
Процесс синтаксического анализа можно разделить на два подпроцесса: лексический анализ и синтаксический анализ.
Лексический анализ — это процесс разделения входного содержимого на большое количество токенов. Токены — это слова в языке, единицы, из которых состоит контент. В человеческом языке это эквивалентно слову в языковом словаре.
Разбор — это процесс применения грамматических правил языка.
Синтаксический анализатор обычно делит работу по синтаксическому анализу на следующие два компонента: лексер (иногда называемый токенизатором), который отвечает за разбиение ввода на допустимые токены, и синтаксический анализатор, который анализирует документ в соответствии с грамматическими правилами языковой структуры. для построения дерева разбора. Лексер знает, как отделить посторонние символы, такие как пробелы и символы новой строки.
Парсинг — это итеративный процесс. Как правило, синтаксический анализатор запрашивает у лексера новый токен и пытается сопоставить его с правилом грамматики. Если соответствующее правило найдено, синтаксический анализатор добавляет узел, соответствующий этому маркеру, в дерево синтаксического анализа и переходит к запросу следующего маркера.
Если правил для сопоставления нет, синтаксический анализатор сохраняет маркеры внутри и продолжает запрашивать маркеры, пока не найдет правило, которое соответствует всем сохраненным внутри маркерам. Если соответствующие правила не найдены, синтаксический анализатор выдает исключение. Это означает, что документ недействителен и содержит синтаксические ошибки.
переводить
Во многих случаях дерево синтаксического анализа еще не является конечным продуктом. Синтаксический анализ обычно используется во время перевода, который относится к преобразованию входного документа в другой формат. Компиляция является одним из таких примеров. Компилятор может скомпилировать исходный код в машинный код, сначала разобрав исходный код в дерево синтаксического анализа, а затем транслировав дерево синтаксического анализа в документацию по машинному коду.
Пример разбора
На рисунке 5 мы построили дерево синтаксического анализа из математического выражения. Теперь давайте попробуем определить простой математический язык для демонстрации процесса синтаксического анализа.
Словарь: язык, который мы используем, может содержать целые числа, знаки плюс и минус.
грамматика:
Синтаксическими единицами языка являются выражения, термины и операторы. Используемый нами язык может содержать любое количество выражений. Определение выражения: «элемент», за которым следует «оператор», за которым следует «элемент». Оператор - знак плюс или минус. Элемент представляет собой целое число или выражение. Разберем 2+3-1. Первая подстрока, соответствующая грамматическому правилу, равна 2, что соответствует пятому грамматическому правилу. Вторая подстрока, соответствующая правилу грамматики, — это 2 + 3, что является выражением в соответствии с правилом 3 (термин, за которым следует оператор, а затем термин). Следующее совпадение достигло конца ввода. 2 + 3 - 1 — это выражение, потому что мы уже знаем, что 2 + 3 — это терм, поэтому оно следует правилу «терм, за которым следует оператор, затем терм». 2++ не соответствует ни одному правилу и поэтому является недопустимым вводом.
Формальное определение лексики и грамматики
Слова обычно представляются регулярными выражениями.
Например, наш пример языка может быть определен следующим образом:
INTEGER :0|[1-9][0-9]*
PLUS : +
MINUS: -
Как видите, определение целого числа дано здесь с регулярным выражением.
Грамматики обычно определяются с использованием формата, называемого BNF. Наш пример языка можно определить следующим образом:
expression := term operation term
operation := PLUS | MINUS
term := INTEGER | expression
Ранее мы говорили, что если грамматика языка является контекстно-свободной грамматикой, ее можно разобрать с помощью обычного синтаксического анализатора. Интуитивное определение контекстно-свободной грамматики — это определение, которое может быть полностью выражено в формате BNF.
тип парсера
Существует два основных типа синтаксических анализаторов: синтаксические анализаторы «сверху вниз» и синтаксические анализаторы «снизу вверх». Интуитивно понятно, что нисходящие синтаксические анализаторы начинают с высокоуровневой структуры грамматики и пытаются найти из нее соответствующие структуры. Восходящий синтаксический анализатор начинает с правил нижнего уровня и постепенно преобразует входное содержимое в правила грамматики, пока не будут удовлетворены правила высокого уровня.
Давайте посмотрим, как эти два парсера будут анализировать наш пример:
Нисходящий синтаксический анализатор начал бы с правил высокого уровня: сначала идентифицируйте 2 + 3 как выражение, затем 2 + 3 - 1 как выражение (идентификация выражения включает в себя сопоставление других правил, но отправной точкой является правило самого высокого уровня). .
Восходящий синтаксический анализатор сканирует ввод, находит соответствующее правило и заменяет соответствующий ввод правилом. Подстановка продолжается таким образом до конца ввода. Частично совпавшие выражения сохраняются в стеке парсера.
куча | войти |
---|---|
2 + 3 - 1 | |
пункт | + 3 - 1 |
срок эксплуатации | 3 - 1 |
выражение | - 1 |
оператор выражения | 1 |
выражение |
Такие восходящие синтаксические анализаторы называются синтаксическими анализаторами сдвига-уменьшения, потому что входные данные сдвигаются вправо (представьте, что указатель перемещается от начала к концу ввода) и постепенно сводится к правилам грамматики.
автоматически сгенерированный парсер
Существуют инструменты, которые могут помочь вам сгенерировать парсеры, они называются генераторами парсеров. Вы просто передаете ему грамматику (словарь и правила грамматики) вашего языка, и он сгенерирует соответствующий синтаксический анализатор. Создание парсера требует глубокого понимания парсера, а создание и оптимизация парсера вручную — непростая задача, поэтому генераторы парсеров очень полезны.
WebKit использует два очень известных генератора синтаксических анализаторов: Flex для создания лексеров и Bison для создания синтаксических анализаторов (вы также можете встретить такие псевдонимы, как Lex и Yacc). Входными данными для Flex является файл, содержащий определения регулярных выражений для токенов. Входными данными для Bison являются правила грамматики языка в формате BNF.
парсер HTML
Работа синтаксического анализатора HTML заключается в анализе тегов HTML в дереве синтаксического анализа.
Определение синтаксиса HTML
Словарь и синтаксис HTML определены в спецификациях, созданных организацией W3C. Текущая версия — HTML4, HTML5 находится в процессе обработки.
неконтекстно-свободная грамматика
Как мы уже узнали во введении к процессу синтаксического анализа, грамматики могут быть формально определены в таких форматах, как BNF.
К сожалению, все обычные парсеры не работают с HTML (я не шучу, их можно использовать для парсинга CSS и JavaScript). HTML нелегко определить с помощью контекстно-свободной грамматики, необходимой для синтаксических анализаторов.
Существует формальный формат, в котором может быть определен HTML: DTD (определение типа документа), но это не контекстно-свободная грамматика.
Сначала это может показаться странным: HTML и XML очень похожи. Доступно множество парсеров XML. Существует XML-вариант HTML (XHTML), так в чем же большая разница?
Разница в том, что к HTML относятся более «снисходительно», что позволяет вам опустить некоторые неявно добавленные теги, иногда некоторые открывающие или закрывающие теги и т. д. В отличие от строгого синтаксиса XML, HTML в целом является «мягким» синтаксисом.
Очевидно, что это, казалось бы, незначительное различие на самом деле имеет огромное значение. С одной стороны, именно поэтому HTML так популярен: он учитывает ваши ошибки и упрощает веб-разработку. С другой стороны, это затрудняет написание формальной грамматики. Короче говоря, HTML не может быть легко проанализирован обычными синтаксическими анализаторами (поскольку его грамматика не является контекстно-свободной) и не может быть проанализирован синтаксическими анализаторами XML.
HTML DTD
HTML определяется в формате DTD. Этот формат можно использовать для определения семейства языков SGML. Он включает определения всех разрешенных элементов, их атрибутов и иерархий. Как упоминалось выше, HTML DTD не может представлять собой контекстно-свободную грамматику.
Существует несколько вариантов DTD. Строгий режим полностью соответствует спецификации HTML, в то время как другие режимы поддерживают разметку, используемую предыдущими браузерами. Целью этого является обеспечение обратной совместимости с некоторыми более ранними версиями контента. Последний строгий режим DTD можно найти здесь:Я 3.org/TR/HTML4/body…
DOM
Выходное «дерево синтаксического анализа» синтаксического анализатора представляет собой древовидную структуру элементов DOM и узлов атрибутов. DOM — это сокращение от объектной модели документа. Это объектное представление документа HTML, а также интерфейс между внешним содержимым (например, JavaScript) и элементами HTML. Корневым узлом дерева разбора является объект «Документ».
Между DOM и разметкой существует почти однозначное соответствие. Например, следующая разметка:
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
Можно перевести в следующее дерево DOM:
Как и HTML, DOM определяется организацией W3C. Видетьwww.w3.org/ДОМ/ДОМТР. Этот…HTML-элементы. Определение HTML можно найти здесь:Ууху. Я 3.org/TR/2003/rec…
Дерево здесь содержит узлы DOM, что означает, что дерево состоит из элементов, реализующих определенный интерфейс DOM. Браузер будет иметь некоторые другие свойства для внутреннего использования в конкретной реализации.
алгоритм разбора
Как мы говорили в предыдущих главах, HTML нельзя анализировать с помощью обычного анализатора «сверху вниз» или «снизу вверх».
причина в следующем:
- Толерантный характер языка.
- Браузеры исторически были терпимы к некоторым распространенным недопустимым использованиям HTML.
- Процесс синтаксического анализа требует постоянной итерации. Исходное содержимое обычно не изменяется во время синтаксического анализа, но в HTML теги сценария, включающие document.write, добавляют дополнительные теги, так что процесс синтаксического анализа фактически изменяет ввод.
Поскольку обычные методы синтаксического анализа использовать нельзя, браузеры создают собственные синтаксические анализаторы для анализа HTML.
Спецификация HTML5 подробно описывает алгоритм синтаксического анализа. Этот алгоритм состоит из двух этапов: токенизация и построение дерева.
Токенизация — это процесс лексического анализа, разбивающий входной контент на несколько токенов. Теги HTML включают открывающие теги, закрывающие теги, имена атрибутов и значения атрибутов.
Токенизатор распознает токен, передает его построителю дерева, а затем принимает следующий символ для распознавания следующего токена и так далее до конца ввода.
алгоритм токенизации
Результатом работы алгоритма является HTML-разметка. Алгоритм представлен с помощью конечного автомата. Каждое состояние получает один или несколько символов из входного потока и обновляет следующее состояние на основе этих символов. Текущее состояние токенизации и состояние древовидной структуры влияют на решение о переходе в следующее состояние. Это означает, что даже если полученные символы одинаковы, для следующего правильного состояния будут получены разные результаты в зависимости от текущего состояния. Алгоритм слишком сложен, чтобы описывать его здесь, поэтому давайте воспользуемся простым примером, чтобы помочь вам понять, как он работает.
Базовый пример — токенизировать следующий HTML-код:
<html>
<body>
Hello world
</body>
</html>
Исходное состояние — это состояние данных. Когда встречается символ . Каждый символ, полученный за это время, добавляется к имени нового тега. В этом примере разметка, которую мы создали, является разметкой html.
Когда встречается тег >, текущий тег отправляется, и состояние снова меняется на «состояние данных». Точно так же обрабатываются маркеры. В настоящее время испускаются теги html и body. Теперь мы вернулись к «состоянию данных». Когда получен символ H в Hello world, токены символов создаются и отправляются до тех пор, пока не будет получен
Теперь мы вернулись к «маркировке». Когда получен следующий входной символ /, токен конечного тега создается и изменяется на «состояние имени тега». Мы снова будем сохранять это состояние, пока не будет получен >. Затем будет отправлен новый токен, и он вернется в «состояние данных». Входы обрабатываются одинаково.
алгоритм построения дерева
При создании синтаксического анализатора создается и объект Document. На этапе построения дерева DOM-дерево с корнем Document также постоянно модифицируется, добавляя в него различные элементы. Каждый узел, отправленный токенизатором, обрабатывается построителем дерева. Спецификация определяет соответствующие элементы DOM для каждой разметки, которые создаются при получении соответствующей разметки. Эти элементы добавляются не только в DOM-дерево, но и в стек открытых элементов. Этот стек используется для исправления ошибок вложения и обработки незакрытых тегов. Его алгоритм также может быть описан конечным автоматом. Эти состояния называются «режимом вставки».
Давайте посмотрим на процесс построения дерева для примера ввода:
<html>
<body>
Hello world
</body>
</html>
Входными данными для этапа построения дерева является последовательность токенов с этапа токенизации. Первый режим — «начальный режим». Переключитесь в режим «до html» после получения HTML-разметки и повторно обработайте разметку в этом режиме. Это создает элемент HTMLHtmlElement и прикрепляет его к корневому объекту документа.
Затем состояние изменится на «перед головой». В этот момент мы получаем тег body. Несмотря на то, что в нашем примере нет тега «head», HTMLHeadElement неявно создается и добавляется в дерево.
Теперь мы находимся в режиме «в голове», а затем в режиме «после головы». Тег body повторно обрабатывается, создается и вставляется HTMLBodyElement, а режим изменяется на «в теле».
Теперь получите последовательность токенов символов, сгенерированных из строки «Hello world». Узел «Текст» создается и вставляется при получении первого символа, а к этому узлу добавляются другие символы.
Получение закрывающего тега тела запускает режим «после тела». Теперь мы получим закрывающий тег HTML и перейдем в режим «после после тела». Процесс синтаксического анализа заканчивается, когда получен маркер конца файла.
Действия после разбора
На этом этапе браузер помечает документ как интерактивный и начинает анализировать те сценарии, которые находятся в «отложенном» режиме, то есть те сценарии, которые должны быть выполнены после анализа документа. Затем состояние документа будет установлено как «завершено», и с ним сработает событие «загрузка».
Вы можете увидеть полный алгоритм токенизации и построения дерева в спецификации HTML5.
Отказоустойчивость браузера
Когда вы просматриваете HTML-страницы, вы никогда не увидите ошибку «Неверный синтаксис». Это связано с тем, что браузер исправит любой недопустимый контент, а затем продолжит работу.
В качестве примера возьмем следующий HTML-код:
<html>
<mytag>
</mytag>
<div>
<p>
</div>
Really lousy HTML
</p>
</html>
Здесь нарушено множество синтаксических правил ("mytag" не стандартный тег, неправильная вложенность между элементами "p" и "div" и т.д.), но браузер все равно будет корректно отображать контент, без каких-либо нареканий. . Потому что существует много кода парсера, исправляющего ошибки авторов HTML-страниц.
Механизм обработки ошибок различных браузеров довольно согласован, но, что удивительно, этот механизм не является частью текущей спецификации HTML. Подобно управлению закладками и кнопкам «вперед/назад», это продукт разработки браузеров на протяжении многих лет. Существуют известные недопустимые структуры HTML, общие для многих веб-сайтов, и каждый браузер пытается исправить эти недопустимые структуры так же, как и другие браузеры.
Спецификация HTML5 определяет некоторые из этих требований. WebKit хорошо резюмирует это во вступительном комментарии класса парсера HTML.
Синтаксический анализатор анализирует токенизированный ввод для построения дерева документа. Если документ имеет правильный формат, он сразу переходит к синтаксическому анализу.К сожалению, нам приходится иметь дело с большим количеством искаженных HTML-документов, поэтому синтаксический анализатор должен быть несколько устойчивым к ошибкам.
Как минимум, мы должны иметь возможность обрабатывать следующие состояния ошибок:
- Элементы, которые явно нельзя добавить в какую-то внешнюю разметку. В этом случае мы должны закрыть все теги до тех пор, пока не появится запрещенный элемент, а затем добавить этот элемент.
- Элементы, которые мы не можем добавить напрямую. Очень вероятно, что автор страницы забыл добавить некоторые из этих тегов (или некоторые из них были необязательными). Эти теги могут включать: HTML HEAD BODY TBODY TR TD LI (чего еще не хватает?).
- Добавьте блочный элемент внутрь встроенного элемента. Закрывает все встроенные элементы до тех пор, пока не появится следующий более высокий блочный элемент.
- Если это все еще не работает, закройте все элементы, пока элементы не будут добавлены, или проигнорируйте тег.
Давайте посмотрим на несколько примеров отказоустойчивости WebKit:
- использовал
вместо
Некоторые сайты используют
вместо
. Для совместимости с IE и Firefox WebKit интегрирует его с
Делать то же самое. код показывает, как показано ниже:
if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
reportError(MalformedBRError);
t->beginTag = true;
}
Обратите внимание, что обработка ошибок выполняется внутри и не видна пользователю.
- дискретная таблица Дискретная таблица — это таблица, находящаяся в содержимом других таблиц, но не в какой-либо одной ячейке. Например следующий пример:
<table>
<table>
<tr><td>inner table</td></tr>
</table>
<tr><td>outer table</td></tr>
</table>
WebKit изменит свою иерархию на две одноуровневые таблицы:
<table>
<tr><td>outer table</td></tr>
</table>
<table>
<tr><td>inner table</td></tr>
</table>
код показывает, как показано ниже:
if (m_inStrayTableContent && localName == tableTag){
popBlock(tableTag);
}
WebKit использует стек для хранения содержимого текущего элемента и извлекает внутреннюю таблицу из стека внешней таблицы. Теперь две таблицы становятся братьями и сестрами.
- Вложенные элементы формы Если пользователь поместит другую форму внутри элемента формы, вторая форма будет проигнорирована. код показывает, как показано ниже:
if (!m_currentFormElement) {
m_currentFormElement = new HTMLFormElement(formTag, m_document);
}
- Слишком сложная иерархия разметки Комментарии к коду сделали это очень ясным.
- Неуместные html или закрывающие теги тела
Опять же, комментарии к коду сделали это очень ясным.
Поддерживается очень плохо отформатированный HTML-код. Мы никогда не закрываем тег body, потому что некоторые дурацкие веб-страницы закрываются до окончания фактического документа. Мы выполняем операцию закрытия, вызывая end().if (t->tagName == htmlTag || t->tagName == bodyTag )
return;
Таким образом, авторы веб-страниц должны принять это к сведению, и если вы не хотите, чтобы вас включили в примеры отказоустойчивых фрагментов кода WebKit в качестве встречной инструкции, пожалуйста, также пишите правильно сформированный HTML-код.
Разбор CSS
Помните концепцию синтаксического анализа во введении? В отличие от HTML, CSS — это контекстно-свободная грамматика, которую можно анализировать с помощью различных парсеров, описанных во введении. Фактически спецификация CSS определяет лексику и синтаксис CSS.
Давайте посмотрим несколько примеров: Лексические грамматики (словари) определяются с помощью регулярных выражений для отдельных токенов:
comment \/\*[^*]*\*+([^/*][^*]*\*+)*\/
num [0-9]+|[0-9]*"."[0-9]+
nonascii [\200-\377]
nmstart [_a-z]|{nonascii}|{escape}
nmchar [_a-z0-9-]|{nonascii}|{escape}
name {nmchar}+
ident {nmstart}{nmchar}*
«ident» — это аббревиатура идентификатора, такого как имя класса. «имя» — это идентификатор элемента (обозначается знаком «#»).
Синтаксис описан в формате BNF.
ruleset
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
selector
: simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
;
simple_selector
: element_name [ HASH | class | attrib | pseudo ]*
| [ HASH | class | attrib | pseudo ]+
;
class
: '.' IDENT
;
element_name
: IDENT | '*'
;
attrib
: '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
[ IDENT | STRING ] S* ] ']'
;
pseudo
: ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
;
Объяснение: Это структура набора правил:
div.error , a.error {
color:red;
font-weight:bold;
}
div.error и a.error — это селекторы. Раздел в фигурных скобках содержит правила, применяемые этим набором правил. Формальное определение этой структуры таково:
ruleset
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
Это означает, что набор правил представляет собой либо один селектор, либо несколько (необязательных) селекторов, разделенных запятыми и пробелами (S для пробела). Набор правил состоит из фигурных скобок и одного или нескольких (необязательных) объявлений, разделенных точкой с запятой. «Объявление» и «Селектор» будут определены следующим форматом BNF.
CSS-парсер WebKit
WebKit использует генераторы синтаксических анализаторов Flex и Bison для автоматического создания синтаксических анализаторов из файлов грамматики CSS. Как мы уже говорили во введении к синтаксическим анализаторам, Bison создает синтаксические анализаторы с уменьшением и сдвигом снизу вверх. Firefox использует парсер сверху вниз, написанный человеком. Оба синтаксических анализатора будут анализировать файлы CSS в объекты таблицы стилей, каждый из которых содержит правила CSS. Объекты правила CSS содержат объекты селектора и объявления, а также другие объекты, соответствующие синтаксису CSS.
Порядок, в котором обрабатываются скрипты и таблицы стилей
сценарий
Модель сети синхронная. Авторы веб-страниц хотят, чтобы синтаксический анализатор анализировал и выполнял скрипт, как только встречал тег script. Синтаксический анализ документа будет остановлен до завершения выполнения скрипта. Если сценарий является внешним, процесс синтаксического анализа останавливается до тех пор, пока ресурс не будет синхронно извлечен из сети, прежде чем продолжить. Эта модель используется уже много лет и также указана в спецификациях HTML4 и HTML5. Авторы также могут пометить сценарий как «отложенный», чтобы он не прекращал синтаксический анализ документа, а ждал завершения синтаксического анализа перед его выполнением. В HTML5 добавлена возможность помечать сценарии как асинхронные, чтобы их можно было анализировать и выполнять другими потоками.
готовить
И WebKit, и Firefox реализуют эту оптимизацию. Пока скрипт выполняется, другие потоки анализируют остальную часть документа, находя и загружая другие ресурсы, которые необходимо загрузить по сети. Таким образом, ресурсы могут загружаться по параллельным соединениям, повышая общую скорость. Обратите внимание, что препарсер не изменяет дерево DOM, а передает работу основному парсеру; препарсер разрешает только ссылки на внешние ресурсы, такие как внешние скрипты, таблицы стилей и изображения.
таблица стилей
С другой стороны, имеет разные модели таблиц стилей. Теоретически приложение не изменяет DOM-дерево таблицы стилей, кажется, нет необходимости ждать таблицу стилей и останавливать парсинг документа. Но это влечет за собой проблему, заключающуюся в том, что сценарий будет запрашивать информацию о стиле этапа синтаксического анализа документа. Если бы я не загрузил аналитический стиль, то скрипт получил бы неправильный ответ, с этим явно будет много проблем. Это вроде бы нетипичный случай, но на самом деле очень распространенный. Firefox в процессе загрузки и анализа таблиц стилей блокирует все скрипты. Для WebKit только когда скрипт пытается получить доступ к свойствам стиля таблицы стилей, может быть затронуто воздействие, не загруженное, это запретит скрипт.
Построение дерева рендеринга
Одновременно с построением дерева DOM браузер также строит другую древовидную структуру: дерево рендеринга. Это дерево визуальных элементов в том порядке, в котором они отображаются, и визуальное представление документа. Что он делает, так это позволяет вам рисовать вещи в правильном порядке.
Firefox называет элементы в дереве рендеринга «фреймами». Термин, используемый WebKit, — средство визуализации или объект визуализации. Визуализатор знает, как размещать и рисовать себя и своих потомков. Класс WebKits RenderObject является базовым классом для всех средств визуализации и определяется следующим образом:
class RenderObject{
virtual void layout();
virtual void paint(PaintInfo);
virtual void rect repaintRect();
Node* node; //the DOM node
Renderstyle* style; // the computed style
RenderLayer* containgLayer; //the containing z-index layer
}
Каждый модуль визуализации представляет собой прямоугольную область, обычно соответствующую блоку CSS связанного узла, как описано в спецификации CSS2. Он содержит геометрическую информацию, такую как ширина, высота и положение. На тип блока влияет атрибут стиля display, связанный с узлом (см. раздел о расчетах стиля). Следующий фрагмент кода WebKit описывает, какой тип средства визуализации следует создать для одного и того же узла DOM в зависимости от свойства отображения.
RenderObject* RenderObject::createObject(Node* node, Renderstyle* style)
{
Document* doc = node->document();
RenderArena* arena = doc->renderArena();
...
RenderObject* o = 0;
switch (style->display()) {
case NONE:
break;
case INLINE:
o = new (arena) RenderInline(node);
break;
case BLOCK:
o = new (arena) RenderBlock(node);
break;
case INLINE_BLOCK:
o = new (arena) RenderBlock(node);
break;
case LIST_ITEM:
o = new (arena) RenderListItem(node);
break;
...
}
return o;
}
Тип элемента также имеет значение, например, элементы управления формы и таблицы имеют специальные рамки. В WebKit, если для элемента требуется создать специальный рендерер, метод createRenderer переопределяется. Объект стиля, на который указывает средство визуализации, содержит некоторую независимую от геометрии информацию.
Взаимосвязь между деревом рендеринга и деревом дома
Рендереры соответствуют элементам DOM, но не взаимно однозначно. Невизуальные элементы DOM не вставляются в дерево рендеринга, например элемент «голова». Если элемент имеет значение свойства отображения «нет», он не будет отображаться в дереве рендеринга (но элементы со значением свойства видимости «скрытый» все равно будут отображаться).
Некоторые элементы DOM соответствуют нескольким визуализациям. Как правило, это элементы со сложной структурой, которые не могут быть описаны одним прямоугольником. Например, элемент «выбрать» имеет 3 средства визуализации: один для области отображения, один для раскрывающегося списка и один для кнопки. Если текст разбит на несколько строк из-за того, что он недостаточно широк для размещения в одной строке, новые строки также добавляются как новые средства визуализации.
Другой пример нескольких средств визуализации — недопустимый HTML. Согласно спецификации CSS встроенный элемент может содержать только один из блочных элементов или встроенный элемент. Если присутствует смешанный контент, следует создать средства визуализации анонимных блоков для переноса встроенных элементов.
Существуют объекты рендеринга, которые соответствуют узлам DOM, но не находятся в том же положении в дереве, что и узлы DOM. Элементы с плавающим и абсолютным позиционированием — это просто элементы, находящиеся вне нормального потока, размещенные в другом месте дерева и сопоставленные с реальным фреймом, в то время как замещающий фрейм размещается на месте.
Процесс построения дерева рендеринга
В Firefox уровень представления регистрируется как прослушиватель обновлений DOM. Уровень представления делегирует создание фрейма FrameConstructor, который анализирует стили (см. Вычисления стилей) и создает фреймы.
В WebKit процесс парсинга стилей и создания рендереров называется «прикреплением». У каждого узла DOM есть метод «присоединения». Присоединение выполняется синхронно, и вставка узла в дерево DOM требует вызова метода «присоединить» нового узла.
Обработка тегов html и body создает корневой узел дерева рендеринга. Этот объект рендеринга корневого узла соответствует блоку контейнера в спецификации CSS, который является блоком верхнего уровня, содержащим все остальные блоки. Его размер — это область просмотра, размер области отображения окна браузера. Firefox называет его ViewPortFrame, а WebKit называет его RenderView. Это объект рендеринга, на который указывает документация. Остальная часть дерева рендеринга построена в виде вставок узлов дерева DOM.
расчет стиля
При построении дерева рендеринга вам необходимо рассчитать визуальные свойства каждого объекта рендеринга. Это делается путем вычисления свойств стиля каждого элемента.
Стили включают таблицы стилей из различных источников, встроенные элементы стилей и визуальные атрибуты в HTML (например, атрибут «bgcolor»). Последний из них будет преобразован в соответствии со свойствами стиля CSS.
Источники таблиц стилей включают таблицу стилей браузера по умолчанию, таблицы стилей, предоставленные авторами веб-страниц, и пользовательские таблицы стилей, предоставленные пользователями браузера (браузер позволяет вам определять ваши любимые стили. В Firefox, например, пользователи могут размещать таблицы стилей в папку «Профиль Firefox»).
Расчет стиля имеет следующие трудности:
- Данные стиля представляют собой структуру слишком большого размера, в которой хранится бесчисленное множество свойств стиля, что может вызвать проблемы с памятью.
- Поиск подходящего правила для каждого элемента может вызвать проблемы с производительностью, если его не оптимизировать. Это огромная задача — просмотреть весь список правил для каждого элемента, чтобы найти соответствующие правила. Селекторы могут иметь сложную структуру, что может привести к процессу сопоставления, который на первый взгляд может показаться правильным, но оказывается бесполезным, и необходимо попробовать другие пути сопоставления.
Например, следующий комбинаторный селектор:
div div div div{
...
}
Это означает, что правило применяется к элементам div, которые являются дочерними элементами трех элементов div.
- Применение правил включает довольно сложные каскадные правила (иерархия, используемая для определения этих правил).
Давайте посмотрим, как браузеры справляются с этими проблемами:
Данные общего стиля
Узлы WebKit ссылаются на объекты стиля (Renderstyle). В некоторых случаях эти объекты могут совместно использоваться разными узлами. Эти узлы являются родственными и:
- Элементы должны находиться в одном и том же состоянии мыши (например, один не может находиться в состоянии «:hover», а другой — нет)
- Ни один элемент не имеет идентификатора
- Имена тегов должны совпадать
- атрибут класса должен соответствовать
- Набор сопоставленных свойств должен быть точно таким же
- Состояние ссылки должно совпадать
- состояние фокуса должно совпадать
- Ни на один элемент не должен влиять селектор атрибута, под «влиянием» я подразумеваю любой селектор, соответствующий использованию селектора атрибута в любом месте селектора.
- Элементы не могут иметь встроенных атрибутов стиля.
- Нельзя использовать родственные селекторы. WebCore просто активирует глобальный переключатель, когда встречает любой родственный селектор, и отключает общий доступ к стилю для всего документа (если он существует). Сюда входят селектор + и такие селекторы, как :first-child и :last-child.
Дерево правил Firefox
Чтобы упростить вычисления стилей, Firefox также использует два других дерева: дерево правил и дерево контекста стиля. WebKit также имеет объекты стиля, но они не хранятся в древовидной структуре, как дерево контекста стиля, а просто ссылаются узлами DOM на связанные стили таких объектов.
Контекст стиля содержит конечные значения. Чтобы вычислить эти значения, примените все правила сопоставления в правильном порядке и преобразуйте их из логических в конкретные. Например, если логическое значение представляет собой процент от размера экрана, его необходимо преобразовать в абсолютные единицы. Идея дерева правил действительно изящна, она позволяет разделить эти значения между узлами, чтобы избежать двойных вычислений и сэкономить место.
Все правила сопоставления хранятся в дереве. Узлы более низкого уровня в пути имеют более высокий приоритет. Дерево правил содержит все известные пути сопоставления правил. Хранение правил выполняется лениво. Дерево правил не начинает вычисления для всех узлов, а только добавляет вычисленные пути к дереву правил, когда необходимо вычислить определенный стиль узла.
Идея эквивалентна трактовке путей дерева правил как слов в словаре. Если мы рассчитали следующее дерево правил:
Предположим, нам нужно сопоставить правило для другого элемента в дереве содержимого, и найти соответствующий путь можно так: B — E — I (именно в таком порядке). Так как мы уже рассчитали путь A — B — E — I — L в дереве, этот путь уже есть, что уменьшает объем требуемой сейчас работы. Давайте посмотрим, как деревья правил могут помочь нам сократить объем работы.
Структурное подразделение
Контекст стиля можно разделить на несколько структур. Эти структуры содержат информацию о стиле для определенных категорий, таких как граница или цвет. Свойства в структуре либо наследуются, либо не наследуются. Унаследованные свойства, если они не определены элементом, наследуются от его родителя. Ненаследуемые свойства (также известные как свойства «сброса») используют значения по умолчанию, если они не определены.
Дерево правил помогает нам кэшировать всю структуру, включая рассчитанные конечные значения. Идея предполагает, что базовый узел не предоставляет определение структуры, и можно использовать кэшированную структуру в верхнем узле.
Вычислить контекст стиля, используя дерево правил
При вычислении контекста стиля для конкретного элемента мы сначала вычисляем соответствующий путь в дереве правил или используем существующий путь. Затем мы применяем правила вдоль этого пути, заполняя структуру в контексте нового стиля. Мы начинаем с нижнего узла пути с наивысшим приоритетом (и обычно с самым особым селектором) и проходим по дереву правил до тех пор, пока структура не будет заполнена. Если узел правила не имеет какой-либо спецификации для этой структуры, то мы можем добиться лучшей оптимизации: найти узел выше в пути, указать полную спецификацию и указать на соответствующий узел после его нахождения. Это лучшая оптимизация, потому что всю структуру можно использовать совместно. Это сокращает вычисление конечных значений и экономит память. Если мы находим частичное определение, мы идем вверх по дереву правил, пока структура не будет заполнена.
Если мы не можем найти какое-либо определение структуры, тогда, если структура имеет «унаследованный» тип, мы укажем на родительскую структуру в дереве контекста, чтобы эту структуру также можно было использовать совместно. В случае сброса структуры типа используется значение по умолчанию.
Если самый специальный узел действительно добавляет значения, нам нужно выполнить некоторые дополнительные вычисления, чтобы преобразовать эти значения в фактические значения. Затем мы кэшируем результат в узле дерева для использования дочерними элементами.
Если элемент и его одноуровневые элементы указывают на один и тот же узел дерева, они могут совместно использовать весь контекст стиля.
Давайте рассмотрим пример, предположим, что у нас есть следующий HTML-код:
<html>
<body>
<div class="err" id="div1">
<p>
this is a <span class="big"> big error </span>
this is also a
<span class="big"> very big error</span> error
</p>
</div>
<div class="err" id="div2">another error</div>
</body>
</html>
Существуют также следующие правила:
div {margin:5px;color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}
Для простоты нам нужно заполнить только две структуры: структуру цвета и структуру полей. Структура цвета содержит только один элемент («цвет»), а структура поля содержит четыре стороны. Сформированное дерево правил показано на следующем рисунке (узлы помечены как «имя узла: указан номер правила»):
Дерево контекста показано на следующем рисунке (имя узла: указывает на узел правила):
Предположим, мы столкнулись со вторым при разборе HTML
Теперь нам нужно заполнить структуру стилей. Первое, что нужно заполнить, это структура маржи. Поскольку последний узел правила (F) не добавляется в структуру поля, нам нужно подниматься по дереву правил, пока мы не найдем структуру кэша, вычисленную при вставке предыдущего узла, а затем использовать эту структуру. Мы найдем эту структуру на самом верхнем узле (т. е. узле B), определяющем правило маржи.
У нас уже есть определение структуры цвета, поэтому мы не можем использовать кэшированную структуру. Поскольку у цвета есть атрибут, нам не нужно подниматься по дереву правил, чтобы заполнить другие атрибуты. Мы вычислим конечные точки (преобразуем строки в RGB и т. д.) и кэшируем вычисленную структуру на этом узле.
второйЭлементы легче обрабатывать. Мы сопоставим правило и в конце концов обнаружим, что оно указывает на правило G, как и предыдущий диапазон. Поскольку мы нашли одноуровневого элемента, указывающего на тот же узел, мы можем совместно использовать весь контекст стиля, просто указав на контекст предыдущего диапазона.
Для структур, содержащих правила, унаследованные от родителя, кэширование выполняется в дереве контекста (на самом деле свойство цвета наследуется, но Firefox рассматривает его как свойство сброса и кэширует его в дереве правил). Например, если мы добавим правило шрифта к абзацу:
p {font-family:Verdana;font size:10px;font-weight:bold}
Затем элемент абзаца, как дочерний элемент div в дереве контекста, будет использовать ту же структуру шрифта, что и его родитель (при условии, что для абзаца не заданы правила шрифта). В WebKit нет дерева правил, поэтому совпадающие объявления перебираются 4 раза. Сначала применяются неважные свойства с высоким приоритетом (свойства, которые следует применять в первую очередь, поскольку они являются основой для других свойств, таких как отображение), за ними следуют важные правила с высоким приоритетом, затем неважные правила с обычным приоритетом и, наконец, важное правило с нормальным приоритетом. Это означает, что несколько вхождений свойств разрешаются в соответствии с правильным порядком наложения. Последний, появившийся, наконец, вступает в силу. Короче говоря, совместное использование объектов стиля (всего объекта или части структуры в объекте) может решить проблемы 1 и 3. Дерево правил Firefox также помогает применять свойства в правильном порядке.
Манипулируйте правилами, чтобы упростить сопоставление
Есть несколько источников правил стиля:
- Правила CSS во внешних таблицах стилей или элементах стиля
p {color:blue}
- встроенные атрибуты стиля и тому подобное
<p style="color:blue" />
- Визуальные атрибуты HTML (сопоставленные с соответствующими правилами стиля)
<p bgcolor="blue" />
Последние два легко сопоставить с элементами, потому что элементы имеют атрибуты стиля, а атрибуты HTML могут быть сопоставлены с использованием элементов в качестве ключей.
Мы упоминали ранее в вопросе 2, что сопоставление правил CSS может быть сложным. Чтобы решить эту дилемму, можно выполнить некоторую обработку правил CSS для облегчения доступа.
После анализа таблицы стилей правила CSS добавляются в хеш-таблицу на основе селектора. Селекторы для этих хэш-таблиц различаются, включая идентификаторы, имена классов, имена тегов и т. д. Существует также общая хеш-таблица для правил, которые не попадают в вышеуказанные категории. Если селектор является идентификатором, правило добавляется в таблицу идентификаторов; если селектор является классом, правило добавляется в таблицу классов и так далее. Эта обработка может значительно упростить сопоставление правил. Нам не нужно просматривать каждое объявление, просто извлеките соответствующие правила для элемента из хеш-таблицы. Этот метод оптимизации исключает более 95% правил, поэтому они вообще не учитываются в процессе сопоставления (4.1).
В качестве примера возьмем следующее правило стиля:
p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}
Первое правило будет вставлено в таблицу классов, второе — в таблицу идентификаторов, а третье — в таблицу тегов.
Для следующего фрагмента HTML:
<p class="error">an error occurred </p>
<div id="messageDiv">this is a message</div>
Сначала мы ищем правило сопоставления для элемента p. В таблице классов есть ключ «ошибка», ниже которого можно найти правила для «p.error». Элемент div имеет связанные с ним правила в таблице идентификаторов (ключ — идентификатор) и таблице тегов. Осталось выяснить, какие правила, выбранные по ключу, действительно совпадают. Например, если соответствующее правило для div выглядит следующим образом:
table div {margin:5px}
Это правило по-прежнему будет извлечено из таблицы разметки, потому что ключ является самым правым селектором, но это правило не будет соответствовать нашему элементу div, поскольку у div нет таблицы-предка.
И WebKit, и Firefox делают это.
Применение правил в правильном каскадном порядке
Объекты стиля имеют свойства, соответствующие каждому визуальному свойству (оба являются свойствами CSS, но являются более общими). Если свойство не определено какими-либо правилами сопоставления, некоторые свойства могут быть унаследованы объектом стиля родительского элемента. Другие свойства имеют значения по умолчанию.
Если вы определите более одного, возникнут проблемы, которые необходимо решить с помощью каскадного порядка.
каскадный порядок таблицы стилей
Объявление свойства стиля может появляться в нескольких таблицах стилей или несколько раз в одной таблице стилей. Это означает, что порядок, в котором применяются правила, чрезвычайно важен. Это называется «каскадным» порядком. Согласно спецификации CSS2 порядок каскадирования (от низкого к высокому приоритету):
- декларация браузера
- Общее заявление пользователя
- Общее заявление автора
- Важное заявление от автора
- Важное замечание для пользователей
Объявление браузера является наименее важным, и пользователи могут заменить заявление автора веб-страницы только пометив объявление как «Важное». Объявления в том же порядке упорядочиваются в соответствии со специфичностью, а затем в указанном порядке. Визуальные атрибуты HTML преобразуются в соответствующие объявления CSS. Они считаются правилами автора веб-страницы с низким приоритетом.
специфичность
Специфика селекторов определяется спецификацией CSS2 следующим образом:
- 1, если объявление исходит из атрибута «стиль», а не правила с селектором, иначе 0 (= а)
- подсчитывается как количество атрибутов ID в селекторе (= b)
- Подсчитывается как количество других атрибутов и псевдоклассов в селекторе (= c)
- обозначают имя элемента и количество псевдоэлементов в селекторе (= d)
Соедините четыре числа как a-b-c-d (в системе больших цифр), чтобы сформировать специфичность.
База, которую вы используете, зависит от наибольшего количества в вышеуказанных категориях. Например, если a=14, вы можете использовать шестнадцатеричный формат. Если a=17, то нужно использовать hex; конечно это маловероятно, если только нет селектора типа этого: html body div div p ... (в селекторе появляется 17 тегов, это крайне маловероятно).
Некоторые примеры:
* {} /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
li {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
li:first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul li {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul ol+li {} /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
h1 + *[rel=up]{} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
ul ol li.red {} /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
li.red.level {} /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
#x34y {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
style="" /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */
Порядок правил
После того, как соответствующие правила найдены, их следует отсортировать в порядке каскадирования. WebKit использует пузырьковую сортировку для небольших списков и сортировку слиянием для больших списков. WebKit реализует сортировку, заменяя оператор «>» для следующих правил:
static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
int spec1 = r1.selector()->specificity();
int spec2 = r2.selector()->specificity();
return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;
}
прогрессивная обработка
WebKit использует флаг, чтобы указать, были ли загружены все таблицы стилей верхнего уровня (включая @imports). Если стиль загружается не полностью во время процесса добавления, используйте заполнитель, отметьте его в документе и пересчитайте его при загрузке таблицы стилей.
макет
Рендереры не содержат информацию о положении и размере, когда они создаются и добавляются в дерево рендеринга. Процесс вычисления этих значений называется компоновкой или перекомпоновкой.
HTML использует поточную модель компоновки, что означает, что в большинстве случаев геометрическая информация может быть вычислена за один проход. Элементы, расположенные позже в потоке, обычно не влияют на геометрию элементов, находящихся раньше в потоке, поэтому макет может перемещаться по документу слева направо и сверху вниз. Но есть исключения, такие как расчеты HTML-таблицы, требующие более одного обхода (3.5).
Система координат устанавливается относительно корневого кадра с использованием верхней и левой координат.
Макет — это рекурсивный процесс. Он начинается с корневого средства визуализации (соответствующего элементу HTML-документа), а затем рекурсивно проходит часть или всю иерархию фреймов, вычисляя информацию о геометрии для каждого средства визуализации, которое необходимо вычислить.
Позиция корневого рендерера — 0,0 левее, а его размеры — это область просмотра (то есть видимая область окна браузера). Все средства визуализации имеют метод «макета» или «перекомпоновки», и каждый модуль визуализации вызывает метод макета своих дочерних элементов, которые необходимо разместить.
Грязный макет
Чтобы избежать общего расположения всех мелких изменений, браузеры используют систему «грязных битов». Макет требуется, если средство визуализации изменяется или помечает себя и свои дочерние элементы как «грязные».
Есть два флага: «грязный» и «дети грязные». "дети грязные" означает, что хоть сам рендерер и не изменился, но у него есть хотя бы один дочерний элемент, который нужно выложить.
Глобальный макет и добавочный макет
Глобальный макет относится к макету, который запускает всю область дерева рендеринга.Причины запуска могут включать:
- Глобальные изменения стиля, влияющие на все средства визуализации, например, изменение размера шрифта.
- Изменение размера экрана.
Макет может быть инкрементальным, то есть выкладываются только грязные рендереры (это может иметь недостаток, заключающийся в необходимости дополнительного макета).
Инкрементный макет запускается асинхронно, когда средство визуализации загрязнено. Например, когда в дерево DOM добавляется дополнительный контент из сети, к дереву рендеринга присоединяется новый модуль визуализации.
Асинхронный макет и синхронный макет
Инкрементная компоновка выполняется асинхронно. Firefox ставит в очередь «команды перекомпоновки» добавочного макета, а планировщик запускает пакетное выполнение этих команд. WebKit также имеет таймеры для выполнения инкрементных макетов: обхода дерева рендеринга и размещения грязных рендереров. Сценарии, запрашивающие информацию о стиле, такую как «offsetHeight», могут синхронно запускать добавочный макет. Глобальные макеты часто запускаются синхронно. Иногда макет запускается как обратный вызов, если некоторые свойства (например, положение прокрутки) изменяются после завершения исходного макета.
оптимизация
Если макет инициируется «изменением размера» или изменением положения (не размера) средства визуализации, размер средства визуализации можно получить из кеша без повторного вычисления. В некоторых случаях изменено только одно поддерево, поэтому нет необходимости начинать компоновку с корневого узла. Это подходит для случаев, когда изменения вносятся локально, не затрагивая окружающие элементы, например, вставка текста в текстовое поле (иначе каждый ввод с клавиатуры запускал бы раскладку из корневого узла).
обработка макета
Макеты обычно имеют следующие шаблоны:
- Родительский рендерер определяет свою ширину.
- Родительский модуль визуализации по очереди обрабатывает дочерние модули визуализации и:
- Разместите субрендерер (установите координаты x,y).
- При необходимости вызовите макет субрендерера (если субрендерер грязный, или это глобальный макет, или по какой-то другой причине), который вычисляет высоту субрендерера.
- Родительский модуль визуализации устанавливает свою собственную высоту на основе накопленной высоты дочерних модулей визуализации и высоты полей и отступов. Это значение также доступно родительскому модулю визуализации родительского модуля визуализации.
- Установите его грязный бит в false.
Firefox использует объект «состояние» (nsHTMLReflowState) в качестве параметра макета (называемого «перекомпоновка»), который включает в себя ширину родительского средства визуализации.
Выходные данные макета Firefox представляют собой объект «метрик» (nsHTMLReflowMetrics), который содержит рассчитанную высоту средства визуализации.
расчет ширины
Ширина средства визуализации рассчитывается на основе ширины блока контейнера, свойства «ширина» в стиле средства визуализации, а также полей и границ.
Например, ширина следующего div:
<div style="width:30%"/>
Будет рассчитываться из Webkit следующим образом (класс Benderbox, метод CALCWIDTH):
- Ширина контейнера принимает большее значение из доступной ширины контейнера и равно 0. availableWidth эквивалентен contentWidth в этом примере и рассчитывается следующим образом:
clientWidth() - paddingLeft() - paddingRight()
clientWidth и clientHeight представляют внутреннюю часть объекта (за исключением границ и полос прокрутки).
- Ширина элемента является атрибутом стиля "width". Он вычисляет абсолютное значение на основе процента ширины контейнера.
- Затем добавьте горизонтальную границу и отступы.
Теперь рассчитывается «предпочтительная ширина». Затем необходимо рассчитать минимальную и максимальную ширину. Если предпочтительная ширина больше максимальной ширины, следует использовать максимальную ширину. Если предпочтительная ширина меньше минимальной ширины (наименьшая неразрывная единица), то следует использовать минимальную ширину.
Эти значения кэшируются для использования, когда требуется макет без изменения ширины.
новая линия
Если средству рендеринга необходимо выполнить перенос во время макета, он немедленно останавливает макет и информирует родительский элемент о необходимости переноса. Родитель создает дополнительные средства визуализации и вызывает для них layout.
рисовать
На этапе рисования система просматривает дерево рендеринга и вызывает метод «paint» модуля рендеринга для отображения содержимого модуля рендеринга на экране. Работа по рисованию выполняется с использованием основных компонентов пользовательского интерфейса.
Глобальная отрисовка и инкрементная отрисовка
Как и верстка, отрисовка делится на глобальную (отрисовку всего дерева рендеринга) и пошаговую. При инкрементной отрисовке изменяется часть средства визуализации, но не затрагивается все дерево. Измененный рендерер делает недействительной соответствующую прямоугольную область на экране, что заставляет ОС рассматривать ее как «грязную область» и генерировать событие «закрашивание». ОС аккуратно объединяет несколько регионов в один. В Chrome ситуация немного сложнее, так как рендерер Chrome не находится на основном процессе. Браузер Chrome в некоторой степени эмулирует поведение ОС. Уровень представления прослушивает эти события и делегирует сообщения корневому узлу рендеринга. Затем дерево рендеринга просматривается до тех пор, пока не будет найден соответствующий рендерер, который перерисовывает себя (обычно также своих дочерних элементов).
порядок розыгрыша
Спецификация CSS2 определяет порядок процесса рисования. Порядок отрисовки на самом деле является порядком, в котором элементы входят в контекст стиля стека. Эти стеки рисуются сзади наперед, поэтому этот порядок влияет на рисование. Порядок укладки рендереров блоков следующий:
- фоновый цвет
- Фоновое изображение
- Рамка
- потомство
- контур
Список отображения Firefox
Firefox просматривает все дерево рендеринга и строит список отображения для нарисованных прямоугольников. Список содержит средства визуализации, связанные с прямоугольником, в правильном порядке прорисовки (сначала фон средства визуализации, затем граница и т. д.). Таким образом, когда придет время перерисовывать, вам нужно будет пройти дерево рендеринга только один раз вместо нескольких обходов (отрисовать все фоны, затем все изображения, затем все границы и т. д.). Firefox оптимизирует этот процесс, не добавляя скрытые элементы, например, полностью закрытые непрозрачными элементами.
прямоугольное хранилище WebKit
Перед перерисовкой WebKit сохраняет исходный прямоугольник как растровое изображение, а затем только рисует разницу между старым и новым прямоугольниками.
Динамические изменения
Когда происходит изменение, браузер попытается дать наименьший возможный ответ. Поэтому при изменении цвета элемента перерисовывается только этот элемент. Когда положение элемента изменяется, только элемент и его дочерние элементы (и, возможно, одноуровневые элементы) размещаются и перерисовываются. Когда добавляется узел DOM, узел размещается и перерисовывается. Некоторые критические изменения (например, увеличение шрифта элемента «html») могут привести к аннулированию кеша, в результате чего все дерево рендеринга будет перекомпоновано и отрисовано.
Потоки движка рендеринга
Механизм рендеринга использует один поток. Почти все операции (кроме сетевых) выполняются в одном потоке. В Firefox и Safari этот поток является основным потоком браузера. В браузере Chrome этот поток является основным потоком процесса вкладки. Сетевые операции могут выполняться несколькими параллельными потоками. Количество одновременных подключений ограничено (обычно от 2 до 6, например, 6 в Firefox 3).
цикл событий
Основной поток браузера — это цикл обработки событий. Это бесконечный цикл, который всегда находится в состоянии принятия, ожидая возникновения событий (таких как события компоновки и рисования) и обрабатывая их. Вот код основного цикла обработки событий в Firefox:
Модель визуализации CSS2
холст
Согласно спецификации CSS2 термин «холст» относится к «пространству, используемому для отображения структур форматирования», то есть к области, в которой браузер рисует содержимое. Размер холста не ограничен, но браузер выбирает начальную ширину в зависимости от размера области просмотра.
согласно сУууу. Я 3.org/TR/CSS2/children…
Блочная модель CSS
Блочная модель CSS описывает прямоугольную рамку, которая создается для элементов в дереве документа и размещается в соответствии с моделью визуального формата. Каждое поле имеет область содержимого (например, текст, изображение и т. д.) и необязательные окружающие области отступов, границ и полей.
Каждый узел генерирует 0..n таких ящиков. Все элементы имеют свойство «отображать», которое определяет тип создаваемого ими поля. Пример:
block - generates a block box.
inline - generates one or more inline boxes.
none - no box is generated.
По умолчанию используется встроенный, но таблица стилей браузера устанавливает другие значения по умолчанию. Например, значением по умолчанию свойства отображения элемента "div" является блок. Вы можете найти примеры таблиц стилей по умолчанию здесь:Уууу, я 3.org/TR/CSS2/Сэм…
Схема позиционирования
Существует три схемы таргетинга:
- Обычный: объект позиционируется в соответствии с его положением в документе, то есть положение объекта в дереве рендеринга аналогично его положению в дереве DOM, и он размещается в соответствии с типом и размером блока.
- Плавающий: объект размещается в обычном потоке, а затем перемещается как можно дальше влево или вправо.
- Абсолютный: положение объекта в дереве рендеринга отличается от его положения в дереве DOM.
Схема позиционирования задается свойствами position и float.
- Если значение статичное и относительное, это нормальный поток.
- Если значение абсолютное и фиксированное, это абсолютное позиционирование.
Статическое позиционирование устраняет необходимость определения положения, но использует позиционирование по умолчанию. Для других схем автору веб-страницы необходимо указать положение: сверху, снизу, слева, справа. Способ выкладки ящиков определяется следующими факторами:
- тип коробки
- размер коробки
-
- Схема позиционирования Внешняя информация, такая как размер изображения и размер экрана
тип коробки
block box: формирует блок, который имеет собственную прямоугольную область в окне браузера.
встроенный блок: не имеет собственного блока, но находится внутри блока-контейнера.
блок находится в вертикальном формате один за другим, а встроенный в горизонтальном формате.
Встроенные блоки размещаются в строках или «линейных блоках». Эти линии должны быть не ниже самого высокого блока и могут быть выше, если блоки выровнены в соответствии с «нижней линией», что означает, что нижняя часть элемента должна быть выровнена в соответствии с тем, где другие блоки не являются нижними. . Если ширины контейнера недостаточно, встроенный элемент будет разделен на несколько строк. Часто это происходит в абзацах.
позиция
относительное позиционирование
Относительное позиционирование: первая позиция нормально, а затем перемещается в соответствии с желаемым смещением.
плавающее позиционирование
Поплавок перемещается влево или вправо от ряда. Интересной особенностью является то, что вокруг него плавают другие ящики. Следующий HTML-код:
<p>
<img style="float:right" src="images/image.gif" width="100" height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
Эффект отображения следующий:
Абсолютное и фиксированное позиционирование
Этот макет точно определен и не имеет ничего общего с обычными потоками. Элементы не участвуют в нормальном потоке. Размеры указаны относительно контейнера. В фиксированном положении контейнер является видимой областью.
Многоуровневое отображение
Это определяется свойством CSS z-index. Он представляет собой третье измерение поля, то есть его положение вдоль «оси Z».
Ящики распределены по нескольким стекам (называемым контекстами стека). В каждом стеке более поздние элементы отрисовываются первыми, а затем более ранние элементы сверху, чтобы быть ближе к пользователю. Если есть перекрытие, новый нарисованный элемент перезапишет предыдущий элемент. Стек сортируется по свойству z-index. Ящики со свойством "z-index" образуют локальный стек. Окна просмотра имеют внешние стеки.
Пример:
<style type="text/css">
div {
position: absolute;
left: 2in;
top: 2in;
}
</style>
<p>
<div
style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
</div>
<div
style="z-index: 1;background-color:green;width: 2in; height: 2in;">
</div>
</p>
Результат выглядит следующим образом:
В то время как красный div находится выше в разметке, чем зеленый div (и должен быть нарисован первым в обычном потоке), атрибут z-index имеет приоритет, поэтому он перемещается выше в стеке, удерживаемом корневым полем Location.
Ссылаться на
How Browsers Work: Behind the scenes of modern web browsers
заключительные замечания
На этом весь контент заканчивается, это моя первая статья в Наггетс, надеюсь каждый сможет что-то почерпнуть. Организовать не просто, все ставьте лайки и вперед!