Давайте посмотрим на ключевые структуры данных, которые являются входными и выходными данными для конвейера рендеринга .
Эти структуры данных:
- Деревья фреймов состоят из локальных и удаленных узлов, которые представляют, какие веб-документы находятся в каком процессе рендеринга и в каком рендеринге Blink.
- Дерево неизменяемых фрагментов представляет собой выходные данные (и входные данные) алгоритма ограничения макета.
- Деревья свойств представляют иерархии преобразования, обрезки, эффекта и прокрутки веб-документа. Они используются по всему трубопроводу.
- Списки отображения и фрагменты рисования являются входными данными для алгоритмов растра и наложения слоев.
- Кадры композитора инкапсулируют поверхности, поверхности рендеринга и плитки текстур графического процессора, которые используются для рисования с использованием графического процессора.
Прежде чем перейти к рассмотрению этих структур данных, следующий пример основан на одной из структур обзора архитектуры . Этот пример используется в этом документе с демонстрацией того, как к нему применяются структуры данных.
<!-- Example code -->
<html>
<div style="overflow: hidden; width: 100px; height: 100px;">
<iframe style="filter: blur(3px);
transform: rotateZ(1deg);
width: 100px; height: 300px"
id="one" src="foo.com/etc"></iframe>
</div>
<iframe style="top:200px;
transform: scale(1.1) translateX(200px)"
id="two" src="bar.com"></iframe>
</html>
Каркасные деревья
Иногда Chrome может решить отображать кадр с перекрестным происхождением в процессе рендеринга, отличном от родительского кадра.
В примере кода всего три кадра:
При изоляции сайта Chromium использует два процесса рендеринга для рендеринга этой веб-страницы. Каждый процесс рендеринга имеет собственное представление дерева фреймов для этой веб-страницы:
Кадр, визуализированный в другом процессе, представляется как удаленный кадр. Удаленный кадр содержит минимальную информацию, необходимую для использования в качестве заполнителя при рендеринге, например его размеры. В противном случае удаленный кадр не содержит никакой информации, необходимой для отображения его фактического содержимого.
Напротив, локальный кадр представляет собой кадр, который проходит через стандартный конвейер рендеринга. Локальный фрейм содержит всю информацию, необходимую для преобразования данных этого фрейма (например, дерева DOM и данных стиля) во что-то, что можно визуализировать и отобразить.
Конвейер рендеринга работает на основе детализации локального фрагмента дерева кадров . Рассмотрим более сложный пример с foo.com
в качестве основного фрейма:
<iframe src="bar.com"></iframe>
И следующий подкадр bar.com
:
<iframe src="foo.com/etc"></iframe>
Хотя по-прежнему существует только два средства рендеринга, теперь есть три локальных фрагмента дерева фреймов: два в процессе рендеринга для foo.com
и один в процессе рендеринга для bar.com
:
Чтобы создать один кадр наборщика для веб-страницы, Viz одновременно запрашивает кадр наборщика из корневого кадра каждого из трех локальных деревьев кадров, а затем объединяет их. См. также раздел «Кадры композитора» .
Основной фрейм foo.com
и подкадр foo.com/other-page
являются частью одного и того же дерева фреймов и отображаются в одном и том же процессе. Однако эти два фрейма по-прежнему имеют независимые жизненные циклы документов , поскольку они являются частью разных фрагментов локального дерева фреймов. По этой причине невозможно сгенерировать один кадр композитора для обоих за одно обновление. Процессу рендеринга не хватает информации для объединения кадра компоновщика, сгенерированного для foo.com/other-page
непосредственно в кадр компоновщика для основного кадра foo.com
. Например, родительский фрейм bar.com
вне процесса может влиять на отображение iframe foo.com/other-url
путем преобразования iframe с помощью CSS или перекрытия частей iframe другими элементами в его DOM.
Водопад обновления визуальных свойств
Визуальные свойства, такие как коэффициент масштабирования устройства и размер области просмотра, влияют на визуализируемый результат и должны быть синхронизированы между фрагментами дерева локальных кадров. С корнем каждого фрагмента локального дерева фреймов связан объект виджета. Обновления визуальных свойств передаются в виджет основного фрейма, а затем распространяются на остальные виджеты сверху вниз.
Например, при изменении размера области просмотра:
Этот процесс не является мгновенным, поэтому реплицируемые визуальные свойства также включают в себя токен синхронизации. Компоновщик Viz использует этот токен синхронизации, чтобы дождаться, пока все локальные фрагменты дерева кадров отправят кадр компоновщику с текущим токеном синхронизации. Этот процесс позволяет избежать смешивания кадров композитора с разными визуальными свойствами.
Неизменяемое дерево фрагментов
Дерево неизменяемых фрагментов — это результат этапа компоновки конвейера рендеринга. Он представляет положение и размер всех элементов на странице (без применения преобразований).
Каждый фрагмент представляет собой часть элемента DOM. Обычно на каждый элемент приходится только один фрагмент, но их может быть больше, если он разделен на разные страницы при печати или столбцы в контексте с несколькими столбцами.
После верстки каждый фрагмент становится неизменным и никогда больше не изменяется. Важно отметить, что мы также налагаем несколько дополнительных ограничений. Мы не:
- Разрешите любые ссылки «вверх» в дереве. (Дочерний элемент не может иметь указатель на своего родителя.)
- «пузырьковые» данные вниз по дереву (дочерний элемент считывает информацию только от своих дочерних элементов, а не от родителя).
Эти ограничения позволяют нам повторно использовать фрагмент для последующего макета. Без этих ограничений нам пришлось бы часто регенерировать все дерево, а это дорого.
Большинство макетов обычно представляют собой дополнительные обновления, например веб-приложение обновляет небольшую часть пользовательского интерфейса в ответ на щелчок пользователя по элементу. В идеале макет должен работать только пропорционально тому, что фактически изменилось на экране. Мы можем добиться этого, повторно используя как можно больше частей предыдущего дерева. Это означает (обычно), что нам нужно только восстановить позвоночник дерева.
В будущем этот неизменяемый дизайн может позволить нам делать интересные вещи, например передавать дерево неизменяемых фрагментов через границы потоков, если это необходимо (для выполнения последующих этапов в другом потоке), генерировать несколько деревьев для плавной анимации макета или выполнять параллельные спекулятивные макеты. . Это также дает нам возможность самой многопоточной компоновки.
Элементы встроенного фрагмента
Встроенный контент (преимущественно стилизованный текст) использует немного другое представление. Вместо древовидной структуры с блоками и указателями мы представляем встроенный контент в виде плоского списка, представляющего дерево. Основное преимущество заключается в том, что представление встроенных строк в виде плоского списка является быстрым, полезным для проверки или запроса встроенных структур данных и эффективным использованием памяти. Это чрезвычайно важно для производительности веб-рендеринга, поскольку рендеринг текста очень сложен и может легко стать самой медленной частью конвейера, если его не оптимизировать.
Плоский список создается для каждого контекста встроенного форматирования в порядке поиска в глубину его поддерева встроенного макета. Каждая запись в списке представляет собой кортеж (объект, количество потомков). Например, рассмотрим этот DOM:
<div style="width: 0;">
<span style="color: blue; position: relative;">Hi</span> <b>there</b>.
</div>
Свойству width
присвоено значение 0, чтобы строка переносилась между «Привет» и «Там».
Когда контекст встроенного форматирования для этой ситуации представлен в виде дерева, он выглядит следующим образом:
{
"Line box": {
"Box <span>": {
"Text": "Hi"
}
},
"Line box": {
"Box <b>": {
"Text": "There"
}
},
{
"Text": "."
}
}
Плоский список выглядит так:
- (Линейный ящик, 2)
- (Ящик <диапазон>, 1)
- (Текст «Привет», 0)
- (Линейный ящик, 3)
- (Коробка <b>, 1)
- (Текст «там», 0)
- (Текст ".", 0)
Существует множество потребителей этой структуры данных: API доступности и API геометрии, такие как getClientRects
и contenteditable
. У каждого разные требования. Эти компоненты получают доступ к плоской структуре данных через удобный курсор.
Курсор имеет API, такие как MoveToNext
, MoveToNextLine
, CursorForChildren
. Такое представление курсора очень удобно для текстового контента по нескольким причинам:
- Итерация в порядке поиска в глубину выполняется очень быстро. Это используется очень часто, поскольку оно похоже на движения каретки. Поскольку это плоский список, поиск в глубину просто увеличивает смещение массива, обеспечивая быстрые итерации и локальность памяти.
- Он обеспечивает поиск в ширину, что необходимо, например, при рисовании фона строк и встроенных блоков.
- Зная количество потомков, можно быстро перейти к следующему брату (просто увеличьте смещение массива на это число).
Деревья свойств
DOM — это дерево элементов (плюс текстовые узлы), а CSS может применять к элементам различные стили.
Это проявляется четырьмя способами:
- Макет: входные данные для алгоритма ограничения макета.
- Paint: как раскрасить и растрировать элемент (но не его потомков).
- Визуальные эффекты: эффекты растра/рисования, применяемые к поддереву DOM, такие как преобразования, фильтры и обрезка.
- Прокрутка: обрезка по оси и закругленными углами, а также прокрутка содержащегося поддерева.
Деревья свойств — это структуры данных, которые объясняют, как визуальные эффекты и эффекты прокрутки применяются к элементам DOM. Они предоставляют средства для ответа на такие вопросы, как: где относительно экрана находится данный элемент DOM, учитывая его размер и положение макета? И еще: какую последовательность операций графического процессора следует использовать для применения визуальных эффектов и эффектов прокрутки?
Визуальные эффекты и эффекты прокрутки в Интернете во всей своей красе очень сложны. Таким образом, самое важное, что делают деревья свойств, — это переводят эту сложность в единую структуру данных, которая точно отражает их структуру и значение, и в то же время устраняет остальную сложность DOM и CSS. Это позволяет нам с гораздо большей уверенностью реализовывать алгоритмы компоновки и прокрутки. В частности:
- Потенциально подверженная ошибкам геометрия и другие расчеты могут быть централизованы в одном месте.
- Сложность построения и обновления деревьев свойств вынесена в один этап конвейера рендеринга.
- Гораздо проще и быстрее отправлять деревья свойств в разные потоки и процессы, чем полное состояние DOM, что позволяет использовать их во многих случаях.
- Чем больше вариантов использования, тем больше преимуществ мы можем получить от кэширования геометрии, построенного поверх него, поскольку они могут повторно использовать кэши друг друга.
RenderingNG использует деревья свойств для многих целей, в том числе:
- Отделение композитинга от краски и композитинга от основной нити.
- Определение оптимальной стратегии композитинга/отрисовки.
- Измерение геометрии IntersectionObserver .
- Избегание работы с закадровыми элементами и фрагментами текстур графического процессора.
- Эффективно и точно аннулирует краску и растр.
- Измерение изменения макета и наибольшего содержания контента в Core Web Vitals.
Каждый веб-документ имеет четыре отдельных дерева свойств: преобразование, обрезка, эффект и прокрутка. (*) Дерево преобразований представляет преобразования CSS и прокрутку. (Преобразование прокрутки представлено в виде матрицы двумерного преобразования.) Дерево клипов представляет собой клипы переполнения . Дерево эффектов представляет все остальные визуальные эффекты: непрозрачность, фильтры, маски, режимы наложения и другие виды клипов, такие как путь клипа. Дерево прокрутки представляет информацию о прокрутке, например, как прокрутки соединяются друг с другом; он необходим для выполнения прокрутки в потоке композитора. Каждый узел в дереве свойств представляет собой прокрутку или визуальный эффект, применяемый элементом DOM. Если эффект имеет несколько эффектов, в каждом дереве свойств одного и того же элемента может быть несколько узлов дерева свойств.
Топология каждого дерева подобна разреженному представлению DOM. Например, если есть три элемента DOM с клипами переполнения, то будет три узла дерева клипов, и структура дерева клипов будет следовать отношениям содержащего блока между клипами переполнения. Между деревьями также есть связи. Эти ссылки указывают относительную иерархию DOM и, следовательно, порядок применения узлов. Например, если преобразование элемента DOM находится ниже другого элемента DOM с фильтром, то, конечно, преобразование применяется до фильтра.
Каждый элемент DOM имеет состояние дерева свойств , которое представляет собой кортеж из четырех элементов (преобразование, клип, эффект, прокрутка), который указывает ближайший родительский клип, преобразование и узлы дерева эффектов, которые действуют на этот элемент. Это очень удобно, поскольку благодаря этой информации мы точно знаем список клипов, преобразований и эффектов, которые применяются к этому элементу, и в каком порядке. Это говорит нам, где он находится на экране и как его рисовать.
Пример
( источник )
<html>
<div style="overflow: scroll; width: 100px; height: 100px;">
<iframe style="filter: blur(3px);
transform: rotateZ(1deg);
width: 100px; height: 300px"
id="one" srcdoc="iframe one"></iframe>
</div>
<iframe style="top:200px;
transform: scale(1.1) translateX(200px)" id=two
srcdoc="iframe two"></iframe>
</html>
Для предыдущего примера (который немного отличается от приведенного во введении) вот ключевые элементы созданных деревьев свойств:
Отображение списков и рисование фрагментов
Элемент отображения содержит низкоуровневые команды рисования (см. здесь ), которые можно растрировать с помощью Skia . Элементы отображения обычно просты и включают всего несколько команд рисования, таких как рисование границы или фона. Обход дерева рисования выполняет итерацию по дереву макета и связанным с ним фрагментам, следуя порядку рисования CSS, для создания списка отображаемых элементов.
Например:
<div id="green" style="background:green; width:80px;">
Hello world
</div>
<div id="blue" style="width:100px;
height:100px; background:blue;
position:absolute;
top:0; left:0; z-index:-1;">
</div>
Этот HTML и CSS создадут следующий список отображения, где каждая ячейка является элементом отображения:
Просмотреть предысторию | #blue фон | #green фон | #green встроенный текст |
---|---|---|---|
drawRect размером 800x600 и белым цветом. | drawRect размером 100x100 в позиции 0,0 и синим цветом. | drawRect размером 80x18 в позиции 8,8 и зеленым цветом. | drawTextBlob с позицией 8,8 и текстом «Hello world». |
Список отображаемых элементов упорядочен по порядку. В приведенном выше примере зеленый элемент div находится перед синим элементом div в порядке DOM, но порядок отрисовки CSS требует, чтобы синий элемент div с отрицательным z-индексом рисовался перед ( шаг 3 ) зеленым элементом div ( шаг 4.1 ). Отображаемые элементы примерно соответствуют атомарным шагам спецификации порядка отрисовки CSS. Один элемент DOM может привести к появлению нескольких элементов отображения, например, у #green есть элемент отображения для фона и другой элемент отображения для встроенного текста. Эта степень детализации важна для представления всей сложности спецификации порядка отрисовки CSS, например, чередования, создаваемого отрицательным полем:
<div id="green" style="background:green; width:80px;">
Hello world
</div>
<div id="gray" style="width:35px; height:20px;
background:gray;margin-top:-10px;"></div>
В результате получается следующий список отображения, где каждая ячейка является элементом отображения:
Просмотреть предысторию | #green фон | #gray фон | #green встроенный текст |
---|---|---|---|
drawRect размером 800x600 и белым цветом. | drawRect размером 80x18 в позиции 8,8 и зеленым цветом. | drawRect размером 35x20 в позиции 8,16 и серого цвета. | drawTextBlob с позицией 8,8 и текстом «Hello world». |
Список отображаемых элементов сохраняется и повторно используется при последующих обновлениях. Если объект макета не изменился за время обхода дерева рисования, элементы его отображения копируются из предыдущего списка. Дополнительная оптимизация основана на свойстве спецификации порядка отрисовки CSS: контексты стека рисуются атомарно. Если ни один объект макета не изменился в контексте наложения, обход дерева рисования пропускает контекст наложения и копирует всю последовательность отображаемых элементов из предыдущего списка.
Текущее состояние дерева свойств сохраняется во время обхода дерева свойств, а список элементов отображения группируется в «куски» элементов отображения, которые имеют одно и то же состояние дерева свойств. Это продемонстрировано в следующем примере:
<div id="scroll" style="background:pink; width:100px;
height:100px; overflow:scroll;
position:absolute; top:0; left:0;">
Hello world
<div id="orange" style="width:75px; height:200px;
background:orange; transform:rotateZ(25deg);">
I'm falling
</div>
</div>
В результате получается следующий список отображения, где каждая ячейка является элементом отображения:
Просмотреть предысторию | #scroll фон прокрутки | #scroll встроенного текста | #orange фон | #orange встроенный текст |
---|---|---|---|---|
drawRect размером 800x600 и белым цветом. | drawRect размером 100x100 в позиции 0,0 и розовым цветом. | drawTextBlob с позицией 0,0 и текстом «Привет, мир». | drawRect размером 75x200 в позиции 0,0 и оранжевого цвета. | drawTextBlob с позицией 0,0 и текстом «Я падаю». |
Тогда дерево свойств преобразования и фрагменты рисования будут (для краткости упрощены):
Упорядоченный список фрагментов рисования, которые представляют собой группы отображаемых элементов и состояние дерева свойств, являются входными данными для этапа создания слоев конвейера рендеринга. Весь список фрагментов краски можно было бы объединить в один составной слой и растеризовать вместе, но это потребовало бы дорогостоящей растеризации каждый раз, когда пользователь прокручивает страницу. Для каждого фрагмента рисования можно создать составной слой и растеризовать его индивидуально, чтобы избежать повторной растеризации, но это быстро исчерпает память графического процессора. На этапе создания слоев необходимо найти компромисс между памятью графического процессора и снижением затрат, когда что-то меняется. Хороший общий подход — объединять фрагменты по умолчанию, а не объединять фрагменты рисования, у которых есть состояния дерева свойств, которые, как ожидается, изменятся в потоке компоновщика, например, с помощью прокрутки потока компоновщика или анимации преобразования потока компоновщика.
Предыдущий пример в идеале должен создавать два составных слоя:
- Слой размером 800x600, содержащий команды рисования:
-
drawRect
размером 800x600 и белым цветом. -
drawRect
размером 100x100 в позиции 0,0 и розовым цветом
-
- Комбинированный слой размером 144x224, содержащий команды рисования:
-
drawTextBlob
с позицией 0,0 и текстом «Привет, мир» - перевести 0,18
-
rotateZ(25deg)
-
drawRect
размером 75x200 в позиции 0,0 и оранжевым цветом -
drawTextBlob
с позицией 0,0 и текстом «Я падаю»
-
Если пользователь прокручивает #scroll
, второй составной слой перемещается, но растеризация не требуется.
В примере из предыдущего раздела о деревьях свойств имеется шесть фрагментов краски. Вместе с состояниями дерева свойств (преобразование, обрезка, эффект, прокрутка) они являются:
- Фон документа: прокрутка документа, клип документа, корень, прокрутка документа.
- Горизонтальный, вертикальный и угол прокрутки для div (три отдельных фрагмента рисования): прокрутка документа, клип документа, размытие
#one
, прокрутка документа. - Iframe
#one
:#one
Rotate, клип прокрутки переполнения,#one
размытие, прокрутка div. - Iframe
#two
: масштаб#two
, клип документа, корень, прокрутка документа.
Кадры композитора: поверхности, поверхности рендеринга и плитки текстур графического процессора.
Процессы браузера и рендеринга управляют растеризацией контента, а затем отправляют кадры компоновщика в процесс Viz для представления на экране. Кадры композитора показывают, как сшивать растеризованный контент и эффективно рисовать его с помощью графического процессора.
Плитка
Теоретически процесс рендеринга или композитор процесса браузера может растеризовать пиксели в одну текстуру полного размера области просмотра рендерера и отправить эту текстуру в Viz. Чтобы отобразить ее, компоновщику дисплея нужно будет просто скопировать пиксели из этой единственной текстуры в соответствующую позицию в буфере кадра (например, на экране). Однако, если бы этот композитор захотел обновить хотя бы один пиксель, ему пришлось бы повторно растрировать всю область просмотра и отправить новую текстуру в Viz.
Вместо этого область просмотра разделена на плитки. Отдельный фрагмент текстуры графического процессора поддерживает каждый фрагмент растеризованными пикселями для части области просмотра. Затем средство визуализации может обновить отдельные плитки или даже просто изменить положение существующих плиток на экране. Например, при прокрутке веб-сайта положение существующих плиток смещается вверх, и лишь изредка требуется растеризация новой плитки для содержимого, расположенного дальше по странице.
Квадраты и поверхности
Тайлы текстур графического процессора — это особый вид четырехугольников , который является просто причудливым названием для той или иной категории текстур. Четырехугольник идентифицирует входную текстуру и указывает, как преобразовать и применить к ней визуальные эффекты. Например, обычные плитки содержимого имеют преобразование, указывающее их положение x, y в сетке плиток.
Эти растеризованные тайлы обертываются в проход рендеринга , который представляет собой список четырехугольников. Проход рендеринга не содержит никакой информации о пикселях; вместо этого у него есть инструкции о том, где и как рисовать каждый квадрат, чтобы получить желаемый результат в пикселях. Для каждого фрагмента текстуры графического процессора имеется четырехъядерный процессор . Компоновщику дисплея просто нужно перебирать список четырехугольников, рисуя каждый из них с указанными визуальными эффектами, чтобы получить желаемый результат в пикселях для прохода рендеринга. Композицию четырехугольников отрисовки для прохода рендеринга можно эффективно выполнять на графическом процессоре, поскольку разрешенные визуальные эффекты тщательно выбираются так, чтобы они напрямую соответствовали функциям графического процессора.
Помимо растровых плиток, существуют дополнительные типы четырехугольников. Например, существуют четырехцветные отрисованные четырехугольники , которые вообще не подкреплены текстурой, или четырехугольники отрисовки текстур для текстур, не являющихся плитками, таких как видео или холст.
В кадр наборщика также возможно встроить другой кадр наборщика. Например, компоновщик браузера создает фрейм компоновщика с пользовательским интерфейсом браузера и пустой прямоугольник, в который будет встроено содержимое компоновщика рендеринга. Другой пример — iframe, изолированный от сайта. Это встраивание осуществляется через поверхности .
Когда наборщик отправляет кадр наборщика, он сопровождается идентификатором, называемым идентификатором поверхности , что позволяет другим кадрам наборщика встраивать его по ссылке. Самый новый кадр композитора, отправленный с определенным идентификатором поверхности, сохраняется Viz. Другой кадр наборщика может затем обратиться к нему позже через четырехугольник отрисовки поверхности , и поэтому Viz знает, что рисовать. (Обратите внимание, что четырехугольники отрисовки поверхностей содержат только идентификаторы поверхностей, а не текстуры.)
Промежуточные проходы рендеринга
Некоторые визуальные эффекты, такие как множество фильтров или расширенные режимы наложения, требуют, чтобы два или более четырехугольников были нарисованы на промежуточной текстуре. Затем промежуточная текстура рисуется в буфере назначения на графическом процессоре (или, возможно, в другую промежуточную текстуру), одновременно применяя визуальный эффект. Чтобы обеспечить это, кадр композитора фактически содержит список проходов рендеринга. Всегда существует корневой проход рендеринга, который отрисовывается последним и чье назначение соответствует буферу кадра, и их может быть больше.
Возможность нескольких проходов рендеринга объясняет название «проход рендеринга». Каждый проход должен выполняться на графическом процессоре последовательно, за несколько «проходов», тогда как один проход может быть выполнен за одно массово-параллельное вычисление на графическом процессоре.
Агрегация
В Viz отправляется несколько кадров композитора, и их необходимо отобразить на экране вместе. Это достигается с помощью этапа агрегации, который преобразует их в один агрегированный кадр наборщика. Агрегация заменяет четырехугольники рисования поверхностей указанными ими кадрами композитора. Это также возможность оптимизировать ненужные промежуточные текстуры или контент, находящийся за кадром. Например, во многих случаях кадр компоновщика для iframe, изолированного от сайта, не нуждается в собственной промежуточной текстуре и может быть отрисован непосредственно в буфер кадра с помощью соответствующих четырехугольников отрисовки. На этапе агрегирования такие оптимизации выявляются и применяются на основе глобальных знаний, недоступных отдельным композиторам рендеринга.
Пример
Вот кадры композитора, которые представляют собой пример из начала этого поста.
- поверхность
foo.com/index.html
: id=0- Проход рендеринга 0: рисовать на выходе.
- Проход рендеринга, рисование четырехугольника: рисование с размытием 3 пикселя и обрезка в проход рендеринга 0.
- Проход рендеринга 1:
- Нарисуйте четырехугольники для содержимого плитки
#one
iframe с позициями x и y для каждого.
- Нарисуйте четырехугольники для содержимого плитки
- Проход рендеринга 1:
- Четырехугольник рисования поверхности: с идентификатором 2, нарисованный с масштабированием и преобразованием перемещения.
- Проход рендеринга, рисование четырехугольника: рисование с размытием 3 пикселя и обрезка в проход рендеринга 0.
- Проход рендеринга 0: рисовать на выходе.
- Поверхность пользовательского интерфейса браузера: ID=1.
- Проход рендеринга 0: рисовать на выходе.
- Рисование четырехугольников для пользовательского интерфейса браузера (также в виде плитки)
- Проход рендеринга 0: рисовать на выходе.
- поверхность
bar.com/index.html
: ID=2- Проход рендеринга 0: рисовать на выходе.
- Нарисуйте четырехугольники для содержимого
#two
iframe с позициями x и y для каждого.
- Нарисуйте четырехугольники для содержимого
- Проход рендеринга 0: рисовать на выходе.
Иллюстрации Уны Кравец.