blink layout [перевод]
Tags: webdev
Published: 2020-04-22 07:56:01

Вольный перевод статьи из блога chromium.googlesource "[Blink Layout](https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/layout/README.md)"

# Blink Layout

Директория `Source/core/layout` содержит реализацию объектов разметки. Покрывает следующие состояния жизненного цикла документа:

- LayoutSubtreeChange (`InLayoutSubtreeChange` и `LayoutSubtreeChangeClean`)
- PreLayout (`InPreLayout`)
- PerformLayout (`InPerformLayout`)
- AfterPerformLayout (`AfterPerformLayout` и `LayoutClean`)

Заметьте, что ведется разработка новой Blink layout системы. Смотрите документ [LayoutNG design document](https://docs.google.com/document/d/1uxbDh4uONFQOiGuiumlJBLGgO4KDWB8ZEkp7Rd47fw4/preview).

Код поддерживается командой [layout team](http://dev.chromium.org/teams/layout-team).

## Блочная модель

[Блочная модель CSS](https://www.w3.org/TR/css-box-3/#the-css-box-model) основана на серии вложенных блоков, от наружней к внутренней:

- Margin box
- Border box: основное координатное пространство `LayoutBox`
- Padding box: a.k.a. client box
- Content box

Следуя [css-overflow-3](https://www.w3.org/TR/css-overflow-3/#scrollbar-layout), если скроллбары ничего не перекрывают, они должны быть внедрены между внутренней границей рамки (border) и внешней границей внутреннего отступа (padding).

График ниже был модифицирован на основе графика из спецификации блочной модели css, включая скроллбары:

        |-------------------------------------------------|
        |                                                 |
        |                  margin-top                     |
        |                                                 |
        |    |---------------------------------------|    |
        |    |                                       |    |
        |    |             border-top                |    |
        |    |                                       |    |
        |    |    |--------------------------|--|    |    |
        |    |    |                          |  |    |    |
        |    |    |       padding-top        |##|    |    |
        |    |    |                          |##|    |    |
        |    |    |    |----------------|    |##|    |    |
        |    |    |    |                |    |  |    |    |
        | ML | BL | PL |  content box   | PR |SW| BR | MR |
        |    |    |    |                |    |  |    |    |
        |    |    |    |----------------|    |  |    |    |
        |    |    |                          |  |    |    |
        |    |    |      padding-bottom      |  |    |    |
        |    |    |                          |  |    |    |
        |    |    |--------------------------|--|    |    |
        |    |    |     scrollbar height ####|SC|    |    |
        |    |    |-----------------------------|    |    |
        |    |                                       |    |
        |    |           border-bottom               |    |
        |    |                                       |    |
        |    |---------------------------------------|    |
        |                                                 |
        |                margin-bottom                    |
        |                                                 |
        |-------------------------------------------------|
    
        BL = border-left
        BR = border-right
        ML = margin-left
        MR = margin-right
        PL = padding-left
        PR = padding-right
        SC = scroll corner
        SW = scrollbar width
    

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

## Scroll origin vs. offset vs. position

Если LayoutBox имеет прокручиваемое переполнение (scrollable overflow), он ассоциируется с PaintLayerScrollableArea. Для представления положения верхней левой точки блока содержимого (видимой его части) в координатной системе, определенной верхней левой точкой переполняемого блока, в котором он может прокручиваться полностью к началу его содержимого, PaintLayerScrollableArea [использует “начало прокрутки”.](https://github.com/chromium/chromium/tree/master/third_party/blink/renderer/core/layout#scroll-origin-vs-offset-vs-position)

Для содержимого, которое находится в потоке слева-направо и сверху-вниз, начало прокрутки будет в точке (0, 0), т.е. верхняя левая точка блока содержимого совпадает с верхней левой точкой содержимого переполняемого блока, в котором он может прокручиваться полностью к началу его содержимого.

Для содержимого, которое находится в потоке справа-налево (включая direction:ltr, writing-mode:vertical-rl и flex-direction:row-reverse), x-координата начала прокрутки будет положительной, а для содержимого, которое находится в потоке снизу-вверх (т.е., flex-direction:column-reverse и режим вертикального письма с direction:ltr), положительной будет y-координата.

Во всех случаях, термин ‘scrollOffset’ (или просто ‘offset’) использует тип ScrollOffset и обозначает расстояние прокрутки вьюпорта от его местоположения до начала его содержимого. Термин ‘scrollPosition’ (или просто ‘position’) использует тип FloatPoint и обозначает точку в координатной плоскости, определенную переполняемым блоком.

Визуализация этих концепций:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ej65x5ftsxbrt6yg93zz.png)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nfjnri5j2eh285u3rvbw.png)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/t8q184tx3rmahd2wrxjm.png)

Если блок в потоке справа-налево и имеет скроллбар ортогонального направления (например, вертикальный скроллбар в блоке direction:rtl), размер скроллбара должен быть добавлен к вычислению начала прокрутки. Ниже представлены два примера. Заметьте, что не имеет значения, находится вертикальных скроллбар по правую или левую сторону блока (вертикальный скроллбар обозначен с помощью `|/|`):

                                     блок
                                  содержимого
                                 |<-------->|
                        начало
                      прокрутки
                    |----------->|
                     _______________________
                    |          |/|          |
                    |          |/|          |
                    |          |/|          |
    direction:rtl   |          |/|   блок   |
                    |          |/|          |
                    |          |/|          |
                    |__________|/|__________|
    
                        переполняемый блок
                    |<--------------------->|
    
                                     блок
                                  содержимого
                                 |<-------->|
                        начало
                      прокрутки
                    |----------->|
                     _________________________
                    |            |          |/|
                    |            |          |/|
                    |            |          |/|
    writing-mode:   |            |    блок  |/|
      vertical-rl   |            |          |/|
                    |            |          |/|
                    |____________|__________|/|
    
                        переполняемый блок
                    |<--------------------->|
    

## Координатные пространства

Layout и Paint работают с четырьмя координатными пространствами и часто обращаются к ним (на самом деле, к двум):

- Физические координаты: Соответствует физическому направлению вывода на физический дисплей (экран, распечатанная страница). Обычно используется для рисования, потому логика компоновки, на основе которой происходит рисование, может отдавать значения в этом пространстве. CSS свойства `top`, `right`, `bottom` и `left` также находятся в этом пространстве.
- Логические координаты: Используются для того, чтобы обеспечить обобщенное позиционирование, которое соответствует любым значениями CSS свойств `writing-mode` и `direction`. Свойства `before`, `after`, `start` и `end` находится в этом пространстве. Они также известны, как ‘logical top’, ‘logical bottom’, ‘logical left’ и ‘logical right’ соответственно.
- Физические координаты с перевернутым направлением блочного потока (flipped block-flow direction): То же, что и физические координаты, но применяется для `writing-mode: vertical-rl`, где блок расположен справа-налево, а его позиция перевернута слева-направо относительно оберточного блока. This is essentially a mirror reflection horizontally across the center of a block's containing block.

    Для `writing-mode` значений, отличных от `vertical-rl`, изменения физических координат нет.

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

- Логические координаты без перескоков линейного направления: являются “логическими координатами блока”, не учитывающими направление текста. Например, “LogicalLeft” и “LogicalRight”.

Пример с `writing-mode: vertical-rl; direction: ltr`:

                 'верхняя' / 'начальная' сторона
    
                   направление блочного потока
              <------------------------------------ |
              ------------------------------------- |
              |        c   |          s           | |
    'левая'   |        o   |          o           | |   линейное     'правая'
       /      |        n   |          m           | |  направление      /
    'after'   |        t   |          e           | |                'before'
    сторона   |        e   |                      | |                сторона
              |        n   |                      | |
              |        t   |                      | |
              ------------------------------------- v
    
                    'начальная' / 'конечная' сторона
    

Другом пример с элементом, имеющим относительное (relative) позиционирование:

    <style>
    html {
        writing-mode: vertical-rl;
    }
    </style>
    <div id="container" style="background-color: lightBlue; width: 300px; height: 200px;">
      <div id="relpos" style="position: relative; top: 50px; left: -60px; width: 70px; height: 80px; background-color: red;"></div>
    </div>
    

Их финальное расположение внутри фрейма с разрешением 800x600:

    container: (492, 8  300x200)
    relpos:    (662, 58 70x80)
    

Внешний отступ (margin) в 8px является дефолтным для HTML элемента body. согласно [https://html.spec.whatwg.org/multipage/rendering.html#the-page](https://html.spec.whatwg.org/multipage/rendering.html#the-page).

Диаграмма ниже детально описывает размеры задействованных элементов:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1wlupe3ynkmc6nv20bfo.png)

На зарисовке ниже показан каждый шаг рекурсивного прохода к верхней границе документа:

    LayoutBlockFlow (позиционирован относительно) DIV id='relpos'   0,0    70x80
    
        Применить относительное позиционирование к 'relpos', пока происходит переворот
        внутри контейнера.
    
        170 = 300 (ширина контейнера) - 70 (ширина relpos) - 60 (relpos left)
        50 = relpos top
    
    LayoutBlockFlow DIV id='container'                      170,50 70x80
    
        Поскольку body одной ширины с контейнером, переворот
        не влияет на блок на этом шаге.
    
    LayoutBlockFlow BODY                                    170,50 70x80
    
        Перевернуть внутри html блока, который симметрично больше body на 8px
        из-за дефолтного внешнего отступа.
    
    LayoutBlockFlow HTML                                    178,58 70x80
    
        Перевернуть блок в области просмотра.
    
        662 = 800 (ширина области просмотра) - 316 (ширина html) 
          + 178 (текущий left блока)
    
    LayoutView #document                                    662,58 70x80
    

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

Дополнительные примеры режима письма и комбинаций направлений можно посмотреть [здесь](http://pauljadam.com/demos/csstext.html).

### Перевернутые координаты блочного потока

Суть “переворачивания” значения в зеркальном его отражении внутри блока контейнера таким образом, что переворачивание его дважды приведет к изначальному положению. Таким образом, при работе с задействованной логиком может быть легко случайно перевернуть ненужное, поскольку переворот слишком много раз можно «исправить» еще одним переворотом. Очевидно, это может привести к запутанному и менее производительному коду, поэтому следует позаботиться о том, чтобы понять и задокументировать любые изменения в логике переворачивания.

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

Значения обычно преобразуются в координаты перевернутого блочного потока с помощью набора методов на задействованных объектах разметки. Смотрите на `FlipForWritingMode()`, `FlipForWritingModeForChild()`.

`InlineBox::FlipForWritingMode()` переворачивает входное значение встроенного блока внутри контейнера.

`LayoutBox::FlipForWritingMode()` переворачивает входное значение указанного блока.

`LayoutBox::FlipForWritingModeForChild()` переворачивает входное значение указанного блока, компенсируя ширину и текущую x-позицию указанного дочернего блока. Это может быть полезным для частого шаблона, в котором мы строим точечное местоположение, начиная с текущего местоположения дочернего блока.

Классы и подклассы для `LayoutBox` и `InlineBox`:

- `PhysicalLocation()` возвращает физическое местоположение встроенного блока или контейнера. `(0,0)` это координаты верхней левой точки контейнера. Переворачивание производится на значениях по мере необходимости. В `LayoutBox` для поиска контейнера, если он не был передан в `PhysicalLocation()`, потребуется пройти по всему дереву разметки, что может быть дорогой операцией. `InlineBox::PhysicalLocation()` же будет дорогой операцией только если `InlineBox` находится в режиме перевернутого блочного потока.
- `Location()` возвращает местоположение контейнера или встроенного блока в координатном пространстве "физических координат перевернутого блочного потока". `(0,0)` это координаты верхней левой точки контейнера для обычных направлений `writing-mode` (`horizontal-tb` и `vertical-lr`), и координаты верхней правой точки контейнера для `writing-mode` в перевернутом блочном потоке (`vertical-rl`).

Заметьте, что есть два основных похожих, но немного отличных метода для поиска содержащего блока для элемента:

- `LayoutObject::Container()` возвращает контейнер для элемента, как опредено в CSS.
- `LayoutObject::ContainingBlock()` возвращает неанонимный вложенный тег для элемента. Если контейнер сам является встроенным - вернется его вложенный неанонимный блок. Метод испольузется `PhysicalLocation()`.

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

Более сложные возможности веб платформы, вроде таблиц, flexbox`а или мультиколоночности обычно реализуются поверх этих примитивов вместе с проверками `IsFlippedBlocksWritingMode()`, `IsLeftToRightDirection()`, и `IsHorizontalWritingMode()`. Смотрите `LayoutTableSection::LogicalRectForWritingModeAndDirection()`, `LayoutFlexibleBox::UpdateAutoMarginsInCrossAxis()` или `LayoutMultiColumnFlowThread::FlowThreadTranslationAtPoint()`.

## Geometry mapping

TODO(wkorman): Разработать:

- `mapToVisualRectInAncestorSpace()`
- `mapAncestorToLocal()`
- `Widget` и `FrameView` деревья. Заметьте, что с первым скоро будет покончено [http://crbug.com/637460](http://crbug.com/637460).
- `GeometryMapper` (или просто указать на его секцию в README paint`а). Пока что, смотрите документ [Web page geometries](https://docs.google.com/document/d/1WZKlOSUK4XI0Le0fgCsyUTVw0dTwutZXGWwzlHXewiU/preview).

## Скроллинг

На данный момент уже имеется хороший обзор на [скроллинг в Blink](https://docs.google.com/presentation/d/1pwx0qBW4wSmYAOJxq2gb3SMvSTCHz2L2TFx_bjsvm8E/preview).

*Скроллингом корневого уровня* называется постоянный рефакторинг архитектуры скроллинга в Blink, который делает корневой `PaintLayer` ответственным за скроллинг, который раньше выполнялся с помощью `FrameView`.  Больше деталей здесь [Root Layer Scrolling](https://bit.ly/root-layer-scrolling).

## Словарь

Здесь представлен краткий обзор на ключевые термины относительно блочного потока, внутристрочного потока и ориентации текста. Больше деталей здесь [CSS Writing Modes Level 3](https://www.w3.org/TR/css-writing-modes-3/).

Спецификация [CSS Logical Properties Level 1](https://drafts.csswg.org/css-logical-props/) представляет последние идеи CSSWG относительно именования пространства логических координат. Когда ось подразумевается или не имеет значения, CSSWG опирается на `block-start`, `block-end`, `inline-start` и `inline-end`, или на `start` и `end` .

Заметьте, что большинство кодовой базы Blink предшествует спецификации логических свойств и, таким образом, еще не последовательно ссылается на логическое направление в заявленном порядке, хотя мы хотели бы двигаться в этом направлении. Также взгляните на спецификацию *physical*, *flow-relative* и *line-relative* [асбтрактной блочной терминологии](https://www.w3.org/TR/css-writing-modes-3/#abstract-box).

- `writing-mode`: горизонтальный или вертикаьлный, где вертикальный может находиться в потоке слева-направо или справа-налево. Для вертикального геометрия была смещена. Смотрите `transposed{Rect,Point,Size}()`.
- `direction`/`dir`: “внутристрочное базовое направление” блока. Либо `ltr`, либо `rtl`. Смотрите `isLeftToRightDirection()`.
- `text-orientation`: ориентация текста в строке. Относится только к вертикальным режимам.
- ортогональный поток: когда блок имеет перпендикулярный своему контейнеру режим записи. Может привести к сложным случаям. Смотрите [спецификацию](https://www.w3.org/TR/css-writing-modes-3/#orthogonal-flows).