CSS 布局 API 第 1 级

W3C 首次公开工作草案,

此版本:
https://www.w3.org/TR/2018/WD-css-layout-api-1-20180412/
最新发布版本:
https://www.w3.org/TR/css-layout-api-1/
编辑草案:
https://drafts.css-houdini.org/css-layout-api-1/
反馈:
public-houdini@w3.org 邮件主题为 “[css-layout-api] … 信息主题 …” (存档)
问题跟踪:
GitHub
规范内嵌问题
编辑:
(微软)
(谷歌)
Tab Atkins-Bittner (谷歌)
(谷歌)
(微软)

摘要

本规范描述了一种 API,允许开发人员响应计算样式和 盒模型 以及 盒树 的变化来布局盒子。

本文档状态

本节描述了本文档在发布时的状态。其他文档可能会取代本文档。当前 W3C 发布的文档列表及该技术报告的最新修订版可在 W3C 技术报告索引 https://www.w3.org/TR/ 中找到。

本文档为 首次公开工作草案

作为首次公开工作草案发布并不意味着 W3C 会员对其表示认可。这是一份草案文件,可能会随时更新、替换或废止。引用此文档不应被视为引用最终的工作成果。

建议通过 GitHub Issues 讨论本规范。 提交问题时,请在标题中包含 “css-layout-api”,最好像这样: “[css-layout-api] …评论摘要…”。 所有问题和评论均 存档

本文档由 CSS 工作组 制作。

本文档由依据 W3C 专利政策 运营的一个小组制作。W3C 维护了与该小组交付成果相关的任何专利披露的 公开列表;该页面还包括披露专利的说明。如果个人掌握了他们认为包含 必要声明 的专利,则必须根据 W3C 专利政策第 6 条 披露相关信息。

本文档受 2018 年 2 月 1 日 W3C 流程文件 约束。

1. 介绍

本节非规范性内容。

CSS 的布局阶段负责从片段盒树中生成和定位内容。

本规范描述了一种 API,允许开发人员根据计算的样式和盒树的变化来布局一个盒模型

2. 布局 API 容器

替代值新增了一种<display-inside>属性: layout(<ident>)

layout()
此值会使元素生成一个布局 API 容器盒模型。

布局 API 容器是元素通过<display-inside>计算值layout()生成的盒模型。

一个布局 API 容器为其内容建立一个新的布局 API 格式化上下文。这与建立块格式化上下文相同,但使用作者提供的布局而不是块布局。 例如,浮动不会进入布局 API 容器,布局 API 容器的边距不会与其内容的边距重叠。

布局 API 容器的所有流入子元素称为布局 API 子元素,并使用作者定义的布局进行布局。

布局 API 容器为其内容形成一个包含块,与块容器完全相同[CSS21]

注意: 在规范的未来版本中,可能会有方法来覆盖包含块的行为。

overflow 属性适用于布局 API 容器。详情见 §4.3 Overflow

由于布局完全由作者决定,其他布局模式(例如 flex、block)中使用的属性可能不适用。例如,作者可能不会遵循子元素上的边距属性。

2.1. 布局 API 容器绘制

布局 API 容器的子元素绘制方式与内联块[CSS21]完全相同,除了布局方法返回的顺序(通过childFragments) 替代了原始文档顺序,且z-index 的非auto值即使positionstatic时,也会创建一个层叠上下文。

2.2. 盒树转换

布局 API 子元素可以根据布局选项的值以不同方式表现,childDisplay 由类上的layoutOptions设置。

如果布局选项childDisplay 的值为"block",则该子元素的display值将被块化。这类似于弹性容器网格容器的子元素。 参见[css3-display]

如果布局选项childDisplay 的值为"normal",则不会发生块化。 相反,具有<display-outside>计算值内联的子元素(根内联盒) 在调用layoutNextFragment() 时,将为每一行生成一个布局片段

注意: 这允许作者调整每一行的可用内联大小,并单独定位每一行。

表示根内联盒布局子元素的子元素也有一些额外的转换。

在上述两种情况下,子元素都会变成原子内联

注意: 当用户代理遇到块级盒子时,不会执行任何“内联拆分”或分片。

注意:在以下示例中,“inline-span”将表示为单个布局子元素, 同时“block”和“float”将成为原子内联
<span id="inline-span">
  Text
  <div id="block"></div>
  <div id="float"></div>
  Text
</span>

3. 布局 API 模型和术语

本节概述了提供给作者的布局 API。

当前布局是我们正在为其执行布局的盒模型的布局算法。

父布局盒模型的直接父元素的布局算法(该布局算法正在请求执行当前布局)。

子布局LayoutChild的布局算法,属于当前布局的子元素。

3.1. 布局子元素

[Exposed=LayoutWorklet]
interface LayoutChild {
    readonly attribute StylePropertyMapReadOnly styleMap;

    IntrinsicSizesRequest intrinsicSizes();
    LayoutFragmentRequest layoutNextFragment(LayoutConstraints constraints, ChildBreakToken breakToken);
};

LayoutChild包含以下内部槽位:


LayoutChild表示一个 CSS 生成的盒模型,在布局发生之前(这些盒模型的计算值都不是displaynone)。

LayoutChild本身不包含任何布局信息(如内联或块大小),但可以用来生成包含布局信息的LayoutFragment

作者不能通过此 API 构造LayoutChild,这是在渲染引擎的独立阶段(样式解析后)完成的。

LayoutChild可以由以下元素生成:

注意:以下示例将被放置到三个LayoutChild中:
<style>
  #box::before { content: 'hello!'; }
</style>
<div id="box">A block level box with text.</div>
<img src="..." />
注意:以下示例将被放置到一个LayoutChild中,因为它们共享一个根内联盒
This is a next node, <span>with some additional styling,
that may</span> break over<br>multiple lines.

多个非原子内联的内容将被放置在同一个LayoutChild中,以允许渲染引擎跨元素边界执行文本排版。

注意:以下示例应生成一个布局片段,但它来自三个非原子内联元素:
ع<span style="color: blue">ع</span>ع

一个LayoutChild数组被传递到布局方法中,表示正在布局的当前盒子的子元素。

获取styleMap时,用户代理必须执行以下步骤:
  1. 返回thisStylePropertyMapReadOnly,该值包含在[[styleMap]]内部槽中。

当在LayoutChild上调用layoutNextFragment(constraints, breakToken)方法时,用户代理必须执行以下步骤:
  1. 创建一个新的LayoutFragmentRequest,其内部槽位为:

  2. 返回request

当在LayoutChild上调用intrinsicSizes()方法时,用户代理必须执行以下步骤:
  1. 创建一个新的IntrinsicSizesRequest,其内部槽位为:

  2. 返回request

注意: layoutNextFragment()intrinsicSizes()不会同步运行。有关完整描述,请参见§5.5.1 请求对象

3.1.1. 布局子元素与盒树

每个盒模型都有一个[[layoutChildMap]]内部槽位,这是一个LayoutWorkletGlobalScopeLayoutChild有序映射

当用户代理在给定workletGlobalScopenamebox的情况下获取布局子元素时,必须执行以下步骤:
  1. 确认以下内容:

  2. layoutChildMap设置为box[[layoutChildMap]]

  3. 如果layoutChildMap[workletGlobalScope]不存在,执行以下步骤:

    1. 通过给定的nameworkletGlobalScope,获取布局定义

      确认获取布局定义成功,且definition不是"invalid"

    2. childInputProperties设置为definition子输入属性

    3. 创建一个新的LayoutChild,其内部槽位为:

    4. layoutChildMap[workletGlobalScope]设置为layoutChild

  4. 返回layoutChildMap[workletGlobalScope]的获取结果。

当一个盒模型插入到盒树中时,用户代理可以为所有LayoutWorkletGlobalScope预填充[[layoutChildMap]]

当一个盒模型盒树中移除时,用户代理必须清除[[layoutChildMap]]

当用户代理想要在给定box的情况下更新布局子元素样式时,必须执行以下步骤:
  1. 确认以下内容:

    • box当前已附加到盒树中。

  2. 如果box包含块不是布局 API 容器,则中止这些步骤。

  3. layoutChildMap设置为box[[layoutChildMap]]

  4. layoutChildMap中的每个layoutChild进行以下操作:

    1. styleMap设置为layoutChild[[styleMap]]

    2. 根据box的新计算样式更新styleMap声明

盒模型的计算样式发生变化时,用户代理必须运行更新布局子元素样式算法。

3.2. 布局片段

[Exposed=LayoutWorklet]
interface LayoutFragment {
    readonly attribute double inlineSize;
    readonly attribute double blockSize;

    attribute double inlineOffset;
    attribute double blockOffset;

    readonly attribute any data;

    readonly attribute ChildBreakToken? breakToken;
};

LayoutFragment具有以下内部槽位:


LayoutFragment表示一个 CSS 片段,该片段是在LayoutChild上执行布局后生成的。这是由layoutNextFragment()方法生成的。

LayoutFragment具有inlineSizeblockSize属性,这些属性由相应子元素的布局算法设置。它们表示该 CSS 片段边框盒大小,且相对于当前布局的书写模式。

inlineSizeblockSize属性不可更改。如果当前布局需要不同的inlineSizeblockSize,则作者必须再次执行layoutNextFragment(),并使用不同的参数来获得不同的结果。

当前布局中的作者可以通过设置其inlineOffsetblockOffset属性来定位生成的LayoutFragment。如果作者未设置,它们默认值为零。inlineOffsetblockOffset属性表示LayoutFragment相对于其父元素边框盒的位置,未应用变换或定位(例如,如果片段是相对定位的情况)。

布局算法执行块状布局(按块方向顺序排列片段),同时将其子元素在内联方向上居中对齐。
registerLayout('block-like', class {
    *intrinsicSizes(children, edges, styleMap) {
      const childrenSizes = yield children.map((child) => {
          return child.intrinsicSizes();
      });

      const maxContentSize = childrenSizes.reduce((max, childSizes) => {
          return Math.max(max, childSizes.maxContentSize);
      }, 0) + edges.all.inline;

      const minContentSize = childrenSizes.reduce((max, childSizes) => {
          return Math.max(max, childSizes.minContentSize);
      }, 0) + edges.all.inline;

      return {maxContentSize, minContentSize};
    }

    *layout(children, edges, constraints, styleMap) {
        const availableInlineSize = constraints.fixedInlineSize - edges.all.inline;
        const availableBlockSize = (constraints.fixedBlockSize || Infinity) - edges.all.block;

        const childFragments = [];
        const childConstraints = { availableInlineSize, availableBlockSize };

        const childFragments = yield children.map((child) => {
            return child.layoutNextFragment(childConstraints);
        });

        let blockOffset = edges.all.blockStart;
        for (let fragment of childFragments) {
            // 按块状方式排列片段,并在内联方向居中。
            fragment.blockOffset = blockOffset;
            fragment.inlineOffset = Math.max(
                edges.all.inlineStart,
                (availableInlineSize - fragment.inlineSize) / 2);

            blockOffset += fragment.blockSize;
        }

        const autoBlockSize = blockOffset + edges.all.blockEnd;

        return {
            autoBlockSize,
            childFragments,
        };
    }
});

布局 API 容器可以通过data属性与其他布局 API 容器通信。该属性由data成员在FragmentResultOptions字典中设置。

LayoutFragmentbreakToken指定了LayoutChild最后一次分割的位置。如果breakToken为null,则LayoutChild不会为该token链生成更多LayoutFragmentbreakToken可以传递给layoutNextFragment()函数以为特定子元素生成下一个LayoutFragmentbreakToken不可更改。如果当前布局需要不同的breakToken,作者必须再次执行layoutNextFragment(),并使用不同的参数。

当用户代理想要在给定generatorlayoutFragmentRequestinternalFragment的情况下创建布局片段时,必须执行以下步骤:
  1. targetRealm设置为generator

  2. 创建一个新的LayoutFragment,其包含:

  3. 返回fragment

3.3. 固有尺寸

[Exposed=LayoutWorklet]
interface IntrinsicSizes {
  readonly attribute double minContentSize;
  readonly attribute double maxContentSize;
};

IntrinsicSizes对象具有以下内部槽位:


IntrinsicSizes对象表示一个 CSS 盒模型最小内容尺寸最大内容尺寸。它具有minContentSizemaxContentSize属性,这些属性表示当前布局LayoutChild边框盒最小/最大内容贡献。这些属性相对于当前布局的书写模式的内联方向。

minContentSizemaxContentSize属性不可更改。在当前布局过程中,LayoutChild的这些值不能更改。

下面的示例展示了两个子元素的边框盒固有尺寸。
<style>
.child-0 {
  width: 380px;
  border: solid 10px;
}

.child-1 {
  border: solid 5px;
}

.box {
  display: layout(intrinsic-sizes-example);
  font: 25px/1 Ahem;
}
</style>

<div class="box">
  <div class="child-0"></div>
  <div class="child-1">XXX XXXX</div>
</div>
registerLayout('intrinsic-sizes-example', class {
    *intrinsicSizes(children, edges, styleMap) {
      const childrenSizes = yield children.map((child) => {
          return child.intrinsicSizes();
      });

      childrenSizes[0].minContentSize; // 400, (380+10+10) child has a fixed size.
      childrenSizes[0].maxContentSize; // 400, (380+10+10) child has a fixed size.

      childrenSizes[1].minContentSize; // 100, size of "XXXX".
      childrenSizes[1].maxContentSize; // 200, size of "XXX XXXX".
    }

    *layout() {}
});
当用户代理想要在给定intrinsicSizesRequestinternalIntrinsicSizes的情况下创建固有尺寸对象时,必须执行以下步骤:
  1. 创建一个新的intrinsicSizes IntrinsicSizes对象,包含:

  2. 返回intrinsicSizes

3.4. 布局约束

[Constructor(optional LayoutConstraintsOptions options),Exposed=LayoutWorklet]
interface LayoutConstraints {
    readonly attribute double availableInlineSize;
    readonly attribute double availableBlockSize;

    readonly attribute double? fixedInlineSize;
    readonly attribute double? fixedBlockSize;

    readonly attribute double percentageInlineSize;
    readonly attribute double percentageBlockSize;

    readonly attribute double? blockFragmentationOffset;
    readonly attribute BlockFragmentationType blockFragmentationType;

    readonly attribute any data;
};

dictionary LayoutConstraintsOptions {
    double availableInlineSize = 0;
    double availableBlockSize = 0;

    double fixedInlineSize;
    double fixedBlockSize;

    double percentageInlineSize;
    double percentageBlockSize;

    double blockFragmentationOffset;
    BlockFragmentationType blockFragmentationType = "none";

    any data;
};

enum BlockFragmentationType { "none", "page", "column", "region" };

LayoutConstraints对象被传递给布局方法,表示用于当前布局进行内部布局的所有约束条件。它还用于将可用空间的信息传递给子布局

LayoutConstraints对象具有availableInlineSizeavailableBlockSize属性。这表示布局中应遵守的可用空间,以生成LayoutFragment

注意: 某些布局可能需要生成超出此尺寸的LayoutFragment。例如,一个替换元素父布局应预期这种情况并适当地处理。

父布局可能要求当前布局必须具有特定的尺寸。如果指定了fixedInlineSizefixedBlockSize当前布局应生成具有指定方向上尺寸的LayoutFragment

该布局算法在内联方向上执行类似于弹性盒子的空余空间分配。它创建了子布局约束,指定子元素应具有固定的内联尺寸。
registerLayout('flex-distribution-like', class {
    *intrinsicSizes(children, edges, styleMap) {
      const childrenSizes = yield children.map((child) => {
          return child.intrinsicSizes();
      });

      const maxContentSize = childrenSizes.reduce((sum, childSizes) => {
          return sum + childSizes.maxContentSize;
      }, 0) + edges.all.inline;

      const minContentSize = childrenSizes.reduce((max, childSizes) => {
          return sum + childSizes.minContentSize;
      }, 0) + edges.all.inline;

      return {maxContentSize, minContentSize};
    }

    *layout(children, edges, constraints, styleMap) {

        const availableInlineSize =
            constraints.fixedInlineSize - edges.all.inline;
        const availableBlockSize =
            (constraints.fixedInlineSize || Infinity) - edges.all.block;

        const childConstraints = { availableInlineSize, availableBlockSize };

        const unconstrainedChildFragments = yield children.map((child) => {
            return child.layoutNextFragment(childConstraints);
        });

        const unconstrainedSizes = [];
        const totalSize = unconstrainedChildFragments.reduce((sum, fragment, i) => {
            unconstrainedSizes[i] = fragment.inlineSize;
            return sum + fragment.inlineSize;
        }, 0);

        // 在子元素之间分配剩余空间。
        const remainingSpace = Math.max(0, inlineSize - totalSize);
        const extraSpace = remainingSpace / children.length;

        const childFragments = yield children.map((child, i) => {
            return child.layoutNextFragment({
                fixedInlineSize: unconstrainedSizes[i] + extraSpace,
                availableBlockSize
            });
        });

        // 位置片段。
        let inlineOffset = 0;
        let maxChildBlockSize = 0;
        for (let fragment of childFragments) {
            fragment.inlineOffset = inlineOffset;
            fragment.blockOffset = edges.all.blockStart;

            inlineOffset += fragment.inlineSize;
            maxChildBlockSize = Math.max(maxChildBlockSize, fragment.blockSize);
        }

        return {
            autoBlockSize: maxChildBlockSize + edges.all.block,
            childFragments,
        };
    }
});

LayoutConstraints对象具有percentageInlineSizepercentageBlockSize属性。这些属性表示在执行布局时百分比应解析的尺寸。

LayoutConstraints对象具有blockFragmentationType属性。当前布局应生成一个在blockFragmentationOffset处进行分片的LayoutFragment,如果可能的话。

当前布局可以根据blockFragmentationType选择不对某个LayoutChild进行分片,例如,如果该子元素具有类似于break-inside: avoid-page;的属性。

当用户代理想要在给定sizingModeboxinternalLayoutConstraints的情况下创建布局约束对象时,必须执行以下步骤:
  1. 如果sizingMode"block-like",则:

    1. fixedInlineSize设置为计算box边框盒内联尺寸的结果(相对于box的书写模式),就像块容器一样。

    2. 如果box块尺寸auto,则将fixedBlockSize设置为null,否则将其设置为计算box边框盒块尺寸的结果,就像块容器一样。

    3. 返回一个新的LayoutConstraints对象,包含:

  2. 如果sizingMode"manual",则:

    1. 返回一个新的LayoutConstraints对象,包含:

3.5. 分割与碎片化

[Exposed=LayoutWorklet]
interface ChildBreakToken {
    readonly attribute BreakType breakType;
    readonly attribute LayoutChild child;
};

[Exposed=LayoutWorklet]
interface BreakToken {
    readonly attribute sequence<ChildBreakToken> childBreakTokens;
    readonly attribute any data;
};

dictionary BreakTokenOptions {
    sequence<ChildBreakToken> childBreakTokens;
    any data = null;
};

enum BreakType { "none", "line", "column", "page", "region" };

LayoutChild可以生成多个LayoutFragment。 如果blockFragmentationType不为none,则LayoutChild可以在块方向上分片。此外,表示内联级内容的LayoutChild可能会逐行分片,如果布局选项childDisplay(由layoutOptions设置)为"normal"

可以使用前一个LayoutFragmentbreakToken生成后续的LayoutFragment。 这告诉子布局从编码在ChildBreakToken中的点开始生成LayoutFragment

此示例显示了一个简单的布局,缩进子片段一定的行数。

此示例还演示了如何使用breakToken生成LayoutChild的下一个片段。

它还演示了如何使用BreakToken来符合LayoutConstraintsblockFragmentationType,它从上一个BreakToken继续布局。 它返回一个带有FragmentResultOptionsbreakToken,用于恢复布局。

registerLayout('indent-lines', class {
    static layoutOptions = {childDisplay: 'normal'};
    static inputProperties = ['--indent', '--indent-lines'];

    *layout(children, edges, constraints, styleMap, breakToken) {

        // 确定我们的(内部)可用尺寸。
        const availableInlineSize =
            constraints.fixedInlineSize - edges.all.inline;
        const availableBlockSize =
            (constraints.fixedBlockSize || Infinity) - edges.all.block;

        // 确定要缩进的行数和缩进量。
        const indent = resolveLength(constraints, styleMap.get('--indent'));
        let lines = styleMap.get('--indent-lines').value;

        const childFragments = [];

        let childBreakToken = null;
        if (breakToken) {
            childBreakToken = breakToken.childBreakTokens[0];

            // 删除所有已为其生成片段的子元素。
            children.splice(0, children.indexOf(childBreakToken.child));
        }

        let blockOffset = edges.all.blockStart;
        let child = children.shift();
        while (child) {
            const shouldIndent = lines-- > 0;

            // 调整缩进的内联尺寸。
            const childAvailableInlineSize = shouldIndent ?
                availableInlineSize - indent : availableInlineSize;

            const childConstraints = {
                availableInlineSize: childAvailableInlineSize,
                availableBlockSize,
                percentageInlineSize: availableInlineSize,
                blockFragmentationType: constraints.blockFragmentationType,
            };

            const fragment = yield child.layoutNextFragment(childConstraints,
                                                            childBreakToken);
            childFragments.push(fragment);

            // 定位片段。
            fragment.inlineOffset = shouldIndent ?
                edges.all.inlineStart + indent : edges.all.inlineStart;
            fragment.blockOffset = blockOffset;
            blockOffset += fragment.blockSize;

            // 检查我们是否已超出块分割限制。
            if (constraints.blockFragmentationType != 'none' &&
                blockOffset > constraints.blockSize) {
                break;
            }

            if (fragment.breakToken) {
                childBreakToken = fragment.breakToken;
            } else {
                // 如果片段没有断点标记,我们将继续处理下一个子元素。
                // 下一个子元素。
                child = children.shift();
                childBreakToken = null;
            }
        }

        const autoBlockSize = blockOffset + edges.all.blockEnd;

        // 返回我们的片段。
        const result = {
            autoBlockSize,
            childFragments: childFragments,
        }

        if (childBreakToken) {
            result.breakToken = {
                childBreakTokens: [childBreakToken],
            };
        }

        return result;
    }
});

3.6. 边缘

[Exposed=LayoutWorklet]
interface LayoutEdgeSizes {
  readonly attribute double inlineStart;
  readonly attribute double inlineEnd;

  readonly attribute double blockStart;
  readonly attribute double blockEnd;

  // Convenience attributes for the sum in one direction.
  readonly attribute double inline;
  readonly attribute double block;
};

[Exposed=LayoutWorklet]
interface LayoutEdges {
  readonly attribute LayoutEdgeSizes border;
  readonly attribute LayoutEdgeSizes scrollbar;
  readonly attribute LayoutEdgeSizes padding;

  readonly attribute LayoutEdgeSizes all;
};

一个 LayoutEdges 对象被传入布局方法。它表示正在布局的当前框的 盒模型边缘 的大小。

这个 LayoutEdges 对象有 borderscrollbarpadding 属性。每个属性都表示其相应边缘的宽度。

这个 LayoutEdges 对象有 all 属性。它是一个便捷属性,表示 borderscrollbarpadding 边缘的总和。

这个 LayoutEdgeSizes 对象表示每个 抽象方向inlineStartinlineEndblockStartblockEnd)中边缘的宽度,以CSS像素为单位。

这个 inlineblockLayoutEdgeSizes 对象上的便捷属性,表示该方向的总和。

此示例展示了一个通过CSS样式化的节点,以及它的 LayoutEdges 可能包含的内容。
<style>
.container {
  width: 50px;
  height: 50px;
}

.box {
  display: layout(box-edges);

  padding: 10%;
  border: solid 2px;
  overflow-y: scroll;
}
</style>

<div class="container">
  <div class="box"></div>
</div>
registerLayout('box-edges', class {
    *layout(children, edges, constraints, styleMap, breakToken) {
        edges.padding.inlineStart; // 5 (as 10% * 50px = 5px).
        edges.border.blockEnd; // 2
        edges.scrollbar.inlineEnd; // UA-dependent, may be 0 or >0 (e.g. 16).
        edges.all.block; // 14 (2 + 5 + 5 + 2).
    }
}

4. 与其他模块的交互

本节描述了其他CSS模块如何与CSS布局API进行交互。

4.1. 大小调整

用户代理必须使用 LayoutConstraints 对象与 当前布局 进行通信,以指示他们希望片段的大小。

如果用户代理希望强制对框设置大小,它可以使用 fixedInlineSizefixedBlockSize 属性来实现。

根据 布局选项sizing(由类中的 layoutOptions 设置)的值,布局API容器可以通过不同方式接收大小信息。

如果 布局选项sizing 值为 "block-like",那么传递给 布局API容器LayoutConstraints

如果 布局选项sizing 值为 "manual",则用户代理在布局API容器的大小由所参与的格式化上下文强制设定时,除非提前强制设置特定的大小,否则不应预先计算 fixedInlineSize 和/或 fixedBlockSize,例如:

注意:在下面的示例中,布局API容器 的行内尺寸设置为50。
<style>
  #container {
    width: 100px;
    height: 100px;
    box-sizing: border-box;
    padding: 5px;
  }
  #layout-api {
    display: layout(foo);
    margin: 0 20px;
  }
</style>
<div id="container">
  <div id="layout-api"></div>
</div>

4.1.1. 定位布局大小调整

如果 布局API容器 是脱离文档流定位的,用户代理 必须 解算定位尺寸方程(CSS定位布局3 §8.1 绝对或固定定位、非替换元素的宽度CSS定位布局3 §8.3 绝对或固定定位、非替换元素的高度),并设置合适的 fixedInlineSizefixedBlockSize

注意:在下面的示例中,布局API容器 的行内和块大小被固定为80。
<style>
  #container {
    position: relative;
    width: 100px;
    height: 100px;
  }
  #layout-api {
    display: layout(foo);
    top: 10px;
    bottom: 10px;
    left: 10px;
    right: 10px;
    position: absolute;
  }
</style>
<div id="container">
  <div id="layout-api"></div>
</div>

4.2. 定位

本级别规范中的所有定位由用户代理处理。

因此:

注意:在下面的示例中:
<style>
  #container {
    display: layout(foo);
    position: relative; /* container is a containing block */
    width: 100px;
    height: 100px;
  }
  #child-relative {
    position: relative;
    left: 5px;
    top: 10px;
  }
</style>
<div id="container">
  <div id="child-relative"></div>
  <div id="child-absolute"></div>
</div>

4.3. 溢出

可滚动溢出对于 布局API容器 由用户代理在本规范级别处理。

布局API容器应像块容器那样计算其可滚动溢出。

即使作者的 布局API容器 将片段放置在 可滚动溢出区域内,相对定位或变换可能会导致片段移动,使得它的 可滚动溢出区域不会产生溢出。

4.4. 分片

父布局可以通过设置 blockFragmentationTypeblockFragmentationOffset 来要求 当前布局进行分片。

例如,[css-multicol-1] 布局会将 blockFragmentationType 设置为 "column" 并将 blockFragmentationOffset 设置为需要子片段的地方。

4.5. 对齐

布局API容器 的第一/最后基线集与块容器生成的方式相同(参见 CSS 盒对齐 3 §9.1 确定盒子的基线)。不过,流内子元素的顺序应由布局方法返回的顺序决定(通过 childFragments)而不是文档顺序。

注意:在未来的规范级别中,作者将能够自行定义基线。这将以如下形式出现:

LayoutChild 中查询基线信息。

const fragment = yield child.layoutNextFragment({
  fixedInlineSize: availableInlineSize,
  baselineRequests: ['alphabetic', 'middle'],
});
fragment.baselines.get('alphabetic') === 25 /* 或其他值 */;

父布局 生成基线信息:

registerLayout('baseline-producing', class {
  *layout(children, edges, constraints, styleMap) {
    const result = {baselines: {}};

    for (let baselineRequest of constraints.baselineRequests) {
      // baselineRequest === 'alphabetic' 或其他值。
      result.baselines[baselineRequest] = 25;
    }

    return result;
  }
});

5. 布局

本节描述了CSS布局API如何与用户代理的布局引擎交互。

5.1. 概念

布局定义是一个结构体,描述了 LayoutWorkletGlobalScope 需要的关于作者定义布局的信息(可由layout()函数引用)。它包括:

文档布局定义是一个结构体,描述了文档需要的关于作者定义布局的信息(可由layout()函数引用)。它包括:

5.2. 布局失效

每个盒子都有一个关联的布局有效标志。它可以是布局有效布局无效。初始状态为布局无效

每个盒子都有一个关联的固有尺寸有效标志。它可以是固有尺寸有效固有尺寸无效。初始状态为固有尺寸无效

当用户代理想要使布局函数失效,并给定box时,用户代理必须执行以下步骤:
  1. layoutFunction设为boxlayout()函数。如果它是其他类型的值(如grid),则终止这些步骤。

  2. name设为layoutFunction的第一个参数。

  3. documentDefinition设为获取文档布局定义的结果,给定name

    如果获取文档布局定义失败,或documentDefinition"无效",则终止这些步骤。

  4. inputProperties设为documentDefinition输入属性

  5. childInputProperties设为documentDefinition子输入属性

  6. 对于inputProperties中的每个property,如果property计算值已更改,将该布局有效标志设为布局无效,并将固有尺寸有效标志设为固有尺寸无效

  7. 对于childInputProperties中的每个property,如果property计算值已更改,将该布局有效标志设为布局无效,并将固有尺寸有效标志设为固有尺寸无效

当用户代理重新计算盒子的计算样式时,或该盒子的子项计算样式重新计算时,使布局函数失效 必须被运行。

当由LayoutChild表示的子盒子被添加或移除到盒子树中,或者其布局因计算样式更改或子项更改而失效时,并且这种失效需要向上传播到盒子树,将当前布局有效标志设为布局无效,并将当前固有尺寸有效标志设为固有尺寸无效

布局API容器的计算样式更改,并且此更改影响了LayoutEdges对象内的值时,将盒子的布局有效标志设为布局无效,并将盒子的固有尺寸有效标志设为固有尺寸无效

如果计算样式的更改影响了LayoutConstraints对象内的值,则只需将盒子的固有尺寸有效标志设为固有尺寸无效

注意:由于LayoutConstraints对象仅在布局函数中传递,因此无需使固有尺寸失效。

注意:以下属性的更改可能会影响LayoutEdges对象:

以下属性的更改可能会影响LayoutConstraints对象:

注意:这仅描述了与CSS布局API相关的布局失效。所有盒子从概念上都有一个布局有效标志,这些更改通过盒子树传播。

5.3. 布局 Worklet

layoutWorklet属性允许访问负责与布局相关的所有类的Worklet

layoutWorkletworklet 全局作用域类型LayoutWorkletGlobalScope

partial interface CSS {
    [SameObject] readonly attribute Worklet layoutWorklet;
};

LayoutWorkletGlobalScopelayoutWorklet的全局执行上下文。

[Global=(Worklet,LayoutWorklet),Exposed=LayoutWorklet]
interface LayoutWorkletGlobalScope : WorkletGlobalScope {
    void registerLayout(DOMString name, VoidFunction layoutCtor);
};

5.4. 注册布局

[Exposed=LayoutWorklet]
dictionary LayoutOptions {
  ChildDisplayType childDisplay = "block";
  LayoutSizingMode sizing = "block-like";
};

[Exposed=LayoutWorklet]
enum ChildDisplayType {
    "block",
    "normal",
};

[Exposed=LayoutWorklet]
enum LayoutSizingMode {
    "block-like",
    "manual",
};

文档有一个 映射,存储文档布局定义。初始时该映射为空;当调用 registerLayout(name, layoutCtor) 时填充。

LayoutWorkletGlobalScope 有一个 映射,存储布局定义。初始时该映射为空,当调用 registerLayout(name, layoutCtor) 时填充。

表示 布局 API 容器 的每个 盒子都有一个 映射,存储布局类实例。初始时该映射为空,当用户代理调用 确定内在尺寸生成片段 时填充。

当调用 registerLayout(name, layoutCtor) 方法时,用户代理 必须 执行以下步骤:
  1. 如果 name 是一个空字符串,抛出 一个 TypeError 并终止所有这些步骤。

  2. layoutDefinitionMapLayoutWorkletGlobalScope布局定义 映射。

  3. 如果 layoutDefinitionMap[name] 存在抛出 一个 "InvalidModificationError" DOMException 并终止所有这些步骤。

  4. inputProperties 为一个空的 sequence<DOMString>

  5. inputPropertiesIterableGet(layoutCtor, "inputProperties") 的结果。

  6. 如果 inputPropertiesIterable 不为 undefined,则将 inputProperties 设置为 转换 inputPropertiesIterablesequence<DOMString> 的结果。如果抛出异常,则重新抛出该异常并终止所有这些步骤。

    注意: 由输入属性获取器提供的 CSS 属性列表可以是自定义的或原生的 CSS 属性。

    注意: CSS 属性列表可能包含简写属性。

    注意: 为了使布局类向前兼容,CSS 属性列表还可以包含当前对用户代理无效的属性。例如 margin-bikeshed-property

  7. childInputProperties 为一个空的 sequence<DOMString>

  8. childInputPropertiesIterableGet(layoutCtor, "childInputProperties") 的结果。

  9. 如果 childInputPropertiesIterable 不为 undefined,则将 childInputProperties 设置为 转换 childInputPropertiesIterablesequence<DOMString> 的结果。如果抛出异常,则重新抛出该异常并终止所有这些步骤。

  10. layoutOptionsValueGet(layoutCtor, "layoutOptions") 的结果。

  11. layoutOptions转换 layoutOptionsValueLayoutOptions 的结果。如果抛出异常,则重新抛出该异常并终止所有这些步骤。

  12. 如果 IsConstructor(layoutCtor) 的结果为 false,抛出 一个 TypeError 并终止所有这些步骤。

  13. prototypeGet(layoutCtor, "prototype") 的结果。

  14. 如果 Type(prototype) 的结果不是 Object,抛出 一个 TypeError 并终止所有这些步骤。

  15. intrinsicSizesGet(prototype, "intrinsicSizes") 的结果。

  16. 如果 IsCallable(intrinsicSizes) 的结果为 false,抛出 一个 TypeError 并终止所有这些步骤。

  17. 如果 intrinsicSizes[[FunctionKind]] 内部槽不是 "generator"抛出 一个 TypeError 并终止所有这些步骤。

  18. layoutGet(prototype, "layout") 的结果。

  19. 如果 IsCallable(layout) 的结果为 false,抛出 一个 TypeError 并终止所有这些步骤。

  20. 如果 layout[[FunctionKind]] 内部槽不是 "generator"抛出 一个 TypeError 并终止所有这些步骤。

  21. definition 为一个新的 布局定义,其属性为:

  22. 设置 layoutDefinitionMap[name] 为 definition

  23. 排队任务 以运行以下步骤:

    1. documentLayoutDefinitionMap 为相关的 文档的 文档布局定义 映射

    2. documentDefinition 为一个新的 文档布局定义,其属性为:

    3. 如果 documentLayoutDefinitionMap[name] 存在,执行以下步骤:

      1. existingDocumentDefinition获取 documentLayoutDefinitionMap[name] 的结果。

      2. 如果 existingDocumentDefinition"invalid",终止所有这些步骤。

      3. 如果 existingDocumentDefinitiondocumentDefinition 不相等(即 输入属性子输入属性布局选项 不同),则:

        设置 documentLayoutDefinitionMap[name] 为 "invalid"

        向调试控制台记录错误,指出相同的类已使用不同的 inputPropertieschildInputPropertieslayoutOptions 注册。

    4. 否则,设置 documentLayoutDefinitionMap[name] 为 documentDefinition

注意:类的结构应为:
class MyLayout {
    static get inputProperties() { return ['--foo']; }
    static get childrenInputProperties() { return ['--bar']; }
    static get layoutOptions() {
      return {childDisplay: 'normal', sizing: 'block-like'}
    }

    *intrinsicSizes(children, edges, styleMap) {
        // Intrinsic sizes code goes here.
    }

    *layout(children, edges, constraints, styleMap, breakToken) {
        // Layout code goes here.
    }
}

5.5. 布局引擎

问题:当前的 API 是可迭代生成器形式。基于实现经验和 web 开发者的经验,这可能会更改为基于 Promise 的 API。这两者各有优缺点。

Promises

生成器

5.5.1. 请求对象

[Exposed=LayoutWorklet]
        interface IntrinsicSizesRequest {
        };
        
        [Exposed=LayoutWorklet]
        interface LayoutFragmentRequest {
        };
        
        typedef (IntrinsicSizesRequest or LayoutFragmentRequest)
            LayoutFragmentRequestOrIntrinsicSizesRequest;
        

IntrinsicSizesRequest 具有内部槽:

LayoutFragmentRequest 具有内部槽:


作者提供的布局类上的 layout 方法和固有尺寸方法是生成器函数,而不是常规的 JavaScript 函数。这样做是为了让用户代理支持异步和并行布局引擎。

当作者在 layoutNextFragment() 方法上调用 LayoutChild 时, 用户代理不会同步生成一个 LayoutFragment 来返回给作者的代码。 相反,它会返回一个 LayoutFragmentRequest。 对作者而言,这个对象是完全不透明的,但它包含内部槽,封装了 layoutNextFragment() 方法调用。

当从布局生成器对象中生成一个或多个 LayoutFragmentRequest 时, 用户代理的布局引擎可能与其他工作异步运行该算法,和/或在不同的执行线程上运行。当引擎生成一个或多个 LayoutFragment 时, 用户代理将用生成的 LayoutFragment 来“触发”生成器对象。

同样适用于 intrinsicSizes() 方法。

下面显示了一个用 JavaScript 编写的布局引擎示例。
class LayoutEngine {
  // This function takes the root of the box-tree, a LayoutConstraints object, and a
  // BreakToken to (if paginating for printing for example) and generates a
  // LayoutFragment.
  layoutEntry(rootBox, rootPageConstraints, breakToken) {
    return layoutFragment({
      box: rootBox,
      layoutConstraints: rootPageConstraints,
      breakToken: breakToken,
    });
  }

  // 该函数接收一个 LayoutFragmentRequest 并调用相应的函数
  // layout algorithm to generate the a LayoutFragment.
  layoutFragment(fragmentRequest) {
    const box = fragmentRequest.layoutChild;
    const algorithm = selectLayoutAlgorithmForBox(box);
    const fragmentRequestGenerator = algorithm.layout(
        fragmentRequest.layoutConstraints,
        box.children,
        box.styleMap,
        fragmentRequest.breakToken);

    let nextFragmentRequest = fragmentRequestGenerator.next();

    while (!nextFragmentRequest.done) {
        // 用户代理可能会决定执行布局以生成片段
      // parallel on separate threads. This example performs them synchronously
      // in order.
      let fragments = nextFragmentRequest.value.map(layoutFragment);

      // 用户代理可能会在恢复此布局工作之前中断去执行其他工作(例如垃圾回收)
      // 此示例只是同步执行布局,无法进行中断。
      nextFragmentRequest = fragmentRequestGenerator.next(fragments);
    }

    return nextFragmentRequest.value; // Return the final LayoutFragment.
  }
}

5.6. 执行布局

// This is the final return value from the author defined layout() method.
dictionary FragmentResultOptions {
    double inlineSize = 0;
    double blockSize = 0;
    double autoBlockSize = 0;
    sequence<LayoutFragment> childFragments = [];
    any data = null;
    BreakTokenOptions breakToken = null;
};

dictionary IntrinsicSizesResultOptions {
    double maxContentSize;
    double minContentSize;
};

5.6.1. 确定固有尺寸

确定固有尺寸算法定义了用户代理如何查询作者定义的布局,获取盒子固有尺寸信息。

注意: 确定固有尺寸算法允许用户代理缓存任意数量的先前调用以便重用。

当用户代理希望为给定的boxchildBoxes 确定固有尺寸布局API格式化上下文时,它必须执行以下步骤:
  1. layoutFunctionbox布局()

  2. 如果layoutFunction固有尺寸有效标志固有尺寸有效,则用户代理可以使用先前调用中的固有尺寸。如果是这样,它可以中止所有这些步骤并使用先前的固有尺寸值。

  3. layoutFunction固有尺寸有效标志设置为固有尺寸有效

  4. namelayoutFunction的第一个参数。

  5. documentDefinition获取文档布局定义给定的name的结果。

    如果获取文档布局定义返回失败,或documentDefinition"invalid",则让box回退到流布局,并中止所有这些步骤。

  6. workletGlobalScope为来自布局Worklet工作域的WorkletGlobalScopes列表中的LayoutWorkletGlobalScope

    用户代理必须拥有并从至少两个LayoutWorkletGlobalScope中选择,除非用户代理处于内存限制下。

    注意: 这是为了确保作者不依赖于能够在全局对象上存储状态或不可再生成的类状态。

    用户代理也可以在此时创建一个 WorkletGlobalScope,给定布局Worklet

  7. 运行调用固有尺寸回调给定的nameboxchildBoxesworkletGlobalScope,可选地并行

    注意: 如果用户代理在调用固有尺寸回调时在一个线程上并行运行,它应选择可以在该线程上使用的布局工作域。

当用户代理希望为nameboxchildBoxesworkletGlobalScope调用固有尺寸回调时,它必须执行以下步骤:
  1. definition获取布局定义给定的nameworkletGlobalScope的结果。

    如果获取布局定义返回失败,则让box回退到流布局,并中止所有这些步骤。

  2. layoutInstance获取布局类实例给定boxdefinitionworkletGlobalScope的结果。

    如果获取布局类实例返回失败,则让box回退到流布局,并中止所有这些步骤。

  3. inputPropertiesdefinition输入属性

  4. children为一个新的列表

  5. 对于每个childBoxchildBoxes中执行以下子步骤:

    1. layoutChild获取布局子元素给定workletGlobalScopenamechildBox的结果。

    2. 追加layoutChildchildren

  6. edges为一个新的LayoutEdgeSizes,其填充了box的所有计算值盒模型边缘

  7. styleMap为一个新的StylePropertyMapReadOnly,仅填充了boxinputProperties列表中的计算值

    我们可能希望将styleMap存储在box上,类似于layoutInstance

  8. 在此阶段,如果childrenstyleMap等同于先前的调用,用户代理可以重用先前调用中的固有尺寸。如果是这样,则让固有尺寸为缓存的固有尺寸,并中止所有这些步骤。

  9. intrinsicSizesGeneratorFunctiondefinition固有尺寸生成器函数

  10. intrinsicSizesGenerator调用(intrinsicSizesGeneratorFunctionlayoutInstance,«childrenedgesstyleMap»)的结果。

    如果抛出异常,则让box回退到流布局,并中止所有这些步骤。

  11. intrinsicSizesValue运行生成器给定的intrinsicSizesGenerator"intrinsic-sizes"的结果。

    如果运行生成器返回失败,则让box回退到流布局,并中止所有这些步骤。

  12. intrinsicSizes转换intrinsicSizesValueIntrinsicSizesResultOptions的结果。

    如果抛出异常,则让box回退到流布局,并中止所有这些步骤。

  13. 设置box固有尺寸

5.6.2. 生成片段

生成片段算法定义了用户代理如何为作者定义的布局生成盒子片段

注意: 生成片段算法允许用户代理缓存任意数量的先前调用以便重用。

当用户代理希望为给定的boxchildBoxesinternalLayoutConstraints和可选的internalBreakToken生成布局API格式化上下文片段时,它必须执行以下步骤:
  1. layoutFunctionbox布局()

  2. 如果layoutFunction布局有效标志布局有效,用户代理可以使用先前调用中的固有尺寸。如果是这样,它可以中止所有这些步骤并使用先前的固有尺寸值。

  3. layoutFunction布局有效标志设置为布局有效

  4. namelayoutFunction的第一个参数。

  5. documentDefinition获取文档布局定义给定的name的结果。

    如果获取文档布局定义返回失败,或documentDefinition"invalid",则让box回退到流布局,并中止所有这些步骤。

  6. workletGlobalScope为来自布局LayoutWorkletGlobalScope工作域列表中的一个。

    用户代理必须拥有并从至少两个LayoutWorkletGlobalScope中选择,除非用户代理处于内存限制下。

    注意: 这是为了确保作者不依赖于能够在全局对象上存储状态或无法再生成的类状态。

    用户代理也可以在此时创建一个 WorkletGlobalScope,给定布局Worklet

  7. 运行调用布局回调给定的nameboxchildBoxesinternalLayoutConstraintsinternalBreakTokenworkletGlobalScope,可选地并行

    注意: 如果用户代理在一个线程上调用固有尺寸回调并行运行,它应选择可以在该线程上使用的布局工作域。

当用户代理希望为nameboxchildBoxesinternalLayoutConstraintsinternalBreakTokenworkletGlobalScope调用布局回调时,它必须执行以下步骤:
  1. definition获取布局定义给定nameworkletGlobalScope的结果。

    如果获取布局定义返回失败,则让box回退到流布局,并中止所有这些步骤。

  2. layoutInstance获取布局类实例给定boxdefinitionworkletGlobalScope的结果。

    如果获取布局类实例返回失败,则让box回退到流布局,并中止所有这些步骤。

  3. sizingModedefinition布局选项sizing属性。

  4. inputPropertiesdefinition输入属性

  5. children为一个新的列表

  6. 对于每个childBoxchildBoxes中执行以下子步骤:

    1. layoutChild获取布局子元素给定workletGlobalScopenamechildBox的结果。

    2. 追加layoutChildchildren

  7. edges为一个新的LayoutEdgeSizes,其填充了box的所有计算值盒模型边缘

  8. layoutConstraints创建布局约束对象给定的internalLayoutConstraintsboxsizingMode的结果。

  9. styleMap为一个新的StylePropertyMapReadOnly,仅填充了boxinputProperties列表中的计算值

    我们可能希望将styleMap存储在box上,类似于layoutInstance

  10. breakToken为一个新的BreakToken,其中填充了internalBreakToken的相关信息。

    如果internalBreakToken为null,则令breakToken为null。

  11. 在此阶段,如果childrenstyleMaplayoutConstraintsbreakToken等同于先前的调用,用户代理可以重用先前调用中的片段。如果是这样,则让输出片段为缓存的片段,并中止所有这些步骤。

  12. layoutGeneratorFunctiondefinition布局生成器函数

  13. layoutGenerator调用(layoutGeneratorFunctionlayoutInstance,«childrenedgeslayoutConstraintsstyleMapbreakToken»)的结果。

    如果抛出异常,则让box回退到流布局,并中止所有这些步骤。

  14. fragmentValue运行生成器给定的layoutGenerator"layout"的结果。

    如果运行生成器返回失败,则让box回退到流布局,并中止所有这些步骤。

  15. fragment转换fragmentValueFragmentResultOptions的结果。

    如果抛出异常,则让box回退到流布局,并中止所有这些步骤。

  16. 对于每个childFragmentfragment子片段中执行以下步骤:

    1. 如果childFragment[[generator]]内部插槽不等于layoutGenerator,则让box回退到流布局,并中止所有这些步骤。

  17. 如果sizingMode"block-like"

    • 则:

      1. inlineSizelayoutConstraints固定行内尺寸。(如果我们使用"block-like"的尺寸模式,则必须设置此值)。

      2. blockSize为计算box边框盒块级大小的结果(相对于box的书写模式),如同块容器一样,给定fragment自动块大小作为"固有高度"。

    • 否则(sizingMode"manual"):

      1. inlineSizefragment行内尺寸

      2. blockSizefragment块大小

  18. box返回一个片段,其包含以下内容:

5.6.3. 实用算法

本节指定了确定固有尺寸生成片段算法中通用的算法。

当用户代理希望获取文档布局定义时,它必须执行以下步骤:
  1. documentLayoutDefinitionMap为关联的文档文档布局定义映射。

  2. 如果 documentLayoutDefinitionMap[name] 不 存在,则返回失败并中止所有这些步骤。

  3. 返回获取documentLayoutDefinitionMap[name]的结果。

当用户代理希望获取布局定义时,给定nameworkletGlobalScope,它必须执行以下步骤:
  1. layoutDefinitionMapworkletGlobalScope布局定义映射。

  2. 如果 layoutDefinitionMap[name] 不 存在,则运行以下步骤:

    1. 排队任务以执行以下步骤:

      1. documentLayoutDefinitionMap为关联的文档文档布局定义映射。

      2. 设置documentLayoutDefinitionMap[name]为"invalid"

      3. 用户代理在调试控制台中记录一个错误,指出类未在所有LayoutWorkletGlobalScope中注册。

    2. 返回失败并中止所有这些步骤。

  3. 返回获取layoutDefinitionMap[name]的结果。

当用户代理希望获取布局类实例时,给定boxdefinitionworkletGlobalScope,它必须执行以下步骤:
  1. layoutClassInstanceMapbox布局类实例映射。

  2. layoutInstance获取layoutClassInstanceMap[workletGlobalScope]的结果。如果layoutInstance为null,执行以下步骤:

    1. 如果definition构造函数有效标志为false,则返回失败并中止所有这些步骤。

    2. layoutCtordefinition类构造函数

    3. layoutInstance构造(layoutCtor)的结果。

      如果构造抛出异常,则将definition构造函数有效标志设置为false,然后返回失败并中止所有这些步骤。

    4. 设置layoutClassInstanceMap[workletGlobalScope]为layoutInstance

  3. 返回layoutInstance

当用户代理希望运行生成器时,给定generatorgeneratorType,它必须执行以下步骤:
  1. done布尔值,初始化为false

  2. nextValue为未定义。

  3. 执行以下子步骤,直到donetrue

    1. nextFunction获取generator中的"next"的结果。

    2. 如果IsCallable(nextFunction)的结果为false,抛出TypeError并中止所有这些步骤。

    3. nextResult为调用Invoke(nextFunctiongenerator,«nextValue»)的结果。

      如果抛出异常,返回失败并中止所有这些步骤。

    4. 如果Type(nextResult)的结果不是Object,抛出TypeError并中止所有这些步骤。

    5. requestOrRequests获取(nextResult中的"value"的结果)。

    6. done获取nextResult中的"done"的结果。

    7. 如果GetMethod(requestOrRequests@@iterable)存在,则:

      1. results为一个新的列表

      2. requests转换requestOrRequestssequence<LayoutFragmentRequestOrIntrinsicSizesRequest>的结果。

        如果抛出异常,重新抛出该异常并中止所有这些步骤。

      3. 对于每个 requestrequests中,执行以下子步骤:

        1. result生成生成器结果(requestgeneratorgeneratorType)的结果。

        2. 追加 resultresults

      4. nextValue设置为results

      5. 继续。

    8. request转换(requestOrRequestsLayoutFragmentRequestOrIntrinsicSizesRequest)的结果。

      如果抛出异常,重新抛出异常并中止所有这些步骤。

    9. result生成生成器结果(requestgeneratorgeneratorType)的结果。

      如果生成生成器结果返回失败,返回失败并中止所有这些步骤。

    10. nextValue设置为result

    11. 继续。

    用户代理可以乱序执行上面的循环,并且可以并行执行。但是requestsresults的顺序必须保持一致。

    注意:这允许用户代理在不同线程或异步运行适当的布局算法(例如与其他工作时间切片布局工作)。如果用户代理并行执行循环,则外部循环必须等待所有跨线程任务完成后才能再次调用生成器。它不能向作者返回部分结果。

  4. 返回调用获取nextResult中的"value"的结果。

当用户代理希望生成生成器结果时,给定requestgeneratorgeneratorType,它必须执行以下步骤:
  1. 如果requestIntrinsicSizesRequest,则:

    1. layoutChild为在request上查找内部槽[[layoutChild]]的结果。

    2. box为在layoutChild上查找内部槽[[box]]的结果。

    3. 如果box未附加到盒子树,返回失败并中止所有这些步骤。

      注意:作者可能会保留来自先前调用的LayoutChild。假设一个盒子只能附加到盒子树一次,且不会被重复使用。

    4. internalIntrinsicSizes为用户代理计算的box边框盒最小/最大内容贡献的结果。

    5. 返回创建固有尺寸对象(requestinternalIntrinsicSizes)的结果。

  2. 如果requestLayoutFragmentRequest,并且generatorType"layout",则:

    1. layoutChild为在request上查找内部槽[[layoutChild]]的结果。

    2. box为在layoutChild上查找内部槽[[box]]的结果。

    3. 如果box未附加到盒子树,返回失败并中止所有这些步骤。

      注意:作者可能会保留来自先前调用的LayoutChild。假设一个盒子只能附加到盒子树一次,且不会被重复使用。

    4. childLayoutConstraints为在request上查找内部槽[[layoutConstraints]]的结果。

    5. childBreakToken为在request上查找内部槽[[breakToken]]的结果。

    6. internalFragment为用户代理基于boxchildLayoutConstraintschildBreakToken生成的片段的结果。

    7. 返回创建布局片段(generatorrequestinternalFragment)的结果。

  3. 返回失败(以上分支均未被采用)。

6. 安全性考虑

这些功能没有已知的安全问题。

7. 隐私考虑

这些功能没有已知的隐私问题。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合来表达。规范中的“必须(MUST)”、“不得(MUST NOT)”、“必需(REQUIRED)”、“应(SHALL)”、“不应(SHALL NOT)”、“应当(SHOULD)”、“不应当(SHOULD NOT)”、“推荐(RECOMMENDED)”、“可以(MAY)”和“可选(OPTIONAL)”等关键字,应按照 RFC 2119 中的定义进行解释。不过,为了可读性,这些词语在本规范中不会全部使用大写字母。

本规范的所有文本都是规范性的,除非明确标记为非规范性、示例和注释部分。[RFC2119]

本规范中的示例以“例如”开头,或与规范性文本分开,并用class="example"标记,例如:

这是一个说明性示例。

说明性注释以“注”开头,并与规范性文本分开,使用class="note"标记,例如:

注:这是一个说明性注释。

建议性内容是规范性部分,样式特别,以引起特别关注,并使用<strong class="advisement">标记,例如: 用户代理必须提供一个可访问的替代方案。

一致性类别

本规范的一致性定义分为三类:

样式表
CSS 样式表
渲染器
用户代理(UA),其解释样式表的语义并渲染使用它们的文档。
创作工具
用户代理(UA),用于编写样式表。

样式表如果符合本模块定义的通用 CSS 语法和每个特性定义的单独语法,则其符合本规范。

渲染器如果不仅按照适当的规范解释样式表,还正确解析并渲染文档,同时支持本规范中定义的所有功能,则符合本规范。不过,由于设备限制,用户代理无法正确渲染文档的情况不会使其不符合规范。(例如,用户代理不要求在单色显示器上渲染颜色。)

创作工具如果编写的样式表在语法上符合本模块的通用 CSS 语法和每个特性的单独语法,并且符合本模块描述的所有其他样式表一致性要求,则符合本规范。

部分实现

为了让作者能够利用前向兼容的解析规则分配回退值,CSS 渲染器必须将任何其无可用支持的 at-rules、属性、属性值、关键字和其他语法结构视为无效,并适当地忽略。特别是,用户代理不得选择性地忽略不支持的组件值并保留支持的值在同一个多值属性声明中:如果任何值被视为无效(不支持的值必须视为无效),CSS 要求忽略整个声明。

不稳定和专有功能的实现

为了避免与未来稳定的 CSS 功能发生冲突,CSS 工作组建议遵循最佳实践,以实现不稳定的功能和 CSS 的专有扩展

非实验性实现

一旦规范达到候选推荐阶段,非实验性实现就变得可能,且实现者应发布未加前缀的实现,只要他们能够证明其符合规范正确实现了任何 CR 级别的功能。

为了建立并维护 CSS 在不同实现间的互操作性,CSS 工作组请求非实验性的 CSS 渲染器在发布任何未加前缀的 CSS 功能实现之前,向 W3C 提交实现报告(以及如果需要的话,提交用于该实现报告的测试用例)。提交给 W3C 的测试用例将由 CSS 工作组进行审查和修正。

关于提交测试用例和实现报告的更多信息,请访问 CSS 工作组网站:https://www.w3.org/Style/CSS/Test/。如有问题,请发送至 public-css-testsuite@w3.org 邮件列表。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[CSS-BREAK-3]
Rossen Atanassov; Elika Etemad. CSS 碎片化模块第 3 级. 2017年2月9日. CR. URL: https://www.w3.org/TR/css-break-3/
[CSS-CASCADE-4]
Elika Etemad; Tab Atkins Jr.. CSS 级联与继承第 4 级. 2016年1月14日. CR. URL: https://www.w3.org/TR/css-cascade-4/
[CSS-FLEXBOX-1]
Tab Atkins Jr.; Elika Etemad; Rossen Atanassov. CSS 弹性盒布局模块第 1 级. 2017年10月19日. CR. URL: https://www.w3.org/TR/css-flexbox-1/
[CSS-GRID-1]
Tab Atkins Jr.; Elika Etemad; Rossen Atanassov. CSS 网格布局模块第 1 级. 2017年12月14日. CR. URL: https://www.w3.org/TR/css-grid-1/
[CSS-INLINE-3]
Dave Cramer; Elika Etemad; Steve Zilles. CSS 内联布局模块第 3 级. 2016年5月24日. WD. URL: https://www.w3.org/TR/css-inline-3/
[CSS-OVERFLOW-3]
David Baron; Florian Rivoal. CSS 溢出模块第 3 级. 2016年5月31日. WD. URL: https://www.w3.org/TR/css-overflow-3/
[CSS-PAGE-FLOATS-3]
Johannes Wilm. CSS 页面浮动. 2015年9月15日. WD. URL: https://www.w3.org/TR/css-page-floats-3/
[CSS-POSITION-3]
Rossen Atanassov; Arron Eicholz. CSS 定位布局模块第 3 级. 2016年5月17日. WD. URL: https://www.w3.org/TR/css-position-3/
[CSS-PSEUDO-4]
Daniel Glazman; Elika Etemad; Alan Stearns. CSS 伪元素模块第 4 级. 2016年6月7日. WD. URL: https://www.w3.org/TR/css-pseudo-4/
[CSS-RUBY-1]
Elika Etemad; Koji Ishii. CSS Ruby 布局模块第 1 级. 2014年8月5日. WD. URL: https://www.w3.org/TR/css-ruby-1/
[CSS-SIZING-3]
Tab Atkins Jr.; Elika Etemad. CSS 内在与外在尺寸模块第 3 级. 2018年3月4日. WD. URL: https://www.w3.org/TR/css-sizing-3/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS 语法模块第 3 级. 2014年2月20日. CR. URL: https://www.w3.org/TR/css-syntax-3/
[CSS-TYPED-OM-1]
Shane Stephens; Tab Atkins Jr.. CSS 类型化 OM 第 1 级. 2017年8月1日. WD. URL: https://www.w3.org/TR/css-typed-om-1/
[CSS-VALUES-3]
Tab Atkins Jr.; Elika Etemad. CSS 值与单位模块第 3 级. 2016年9月29日. CR. URL: https://www.w3.org/TR/css-values-3/
[CSS-WRITING-MODES-3]
Elika Etemad; Koji Ishii. CSS 书写模式第 3 级. 2017年12月7日. CR. URL: https://www.w3.org/TR/css-writing-modes-3/
[CSS21]
Bert Bos; et al. 层叠样式表第 2 级修订版(CSS 2.1)规范. 2011年6月7日. REC. URL: https://www.w3.org/TR/CSS2/
[CSS22]
Bert Bos. 层叠样式表第 2 级修订版(CSS 2.2)规范. 2016年4月12日. WD. URL: https://www.w3.org/TR/CSS22/
[CSS3-DISPLAY]
Elika Etemad. CSS 显示模块第 3 级. 2017年7月20日. WD. URL: https://www.w3.org/TR/css-display-3/
[CSSOM-1]
Simon Pieters; Glenn Adams. CSS 对象模型(CSSOM). 2016年3月17日. WD. URL: https://www.w3.org/TR/cssom-1/
[DOM]
Anne van Kesteren. DOM 标准. 现行标准. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. 基础标准. 现行标准. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. 在 RFC 中指示需求级别的关键字. 1997年3月. 当前最佳实践. URL: https://tools.ietf.org/html/rfc2119
[WebIDL]
Cameron McCormack; Boris Zbarsky; Tobie Langel. Web IDL. 2016年12月15日. ED. URL: https://heycam.github.io/webidl/
[WORKLETS-1]
Ian Kilpatrick. 工作线程第 1 级. 2016年6月7日. WD. URL: https://www.w3.org/TR/worklets-1/

参考性引用

[CSS-BACKGROUNDS-3]
Bert Bos; Elika Etemad; Brad Kemper. CSS 背景与边框模块第 3 级. 2017年10月17日. CR. URL: https://www.w3.org/TR/css-backgrounds-3/
[CSS-MULTICOL-1]
Håkon Wium Lie; Florian Rivoal; Rachel Andrew. CSS 多列布局模块第 1 级. 2017年10月5日. WD. URL: https://www.w3.org/TR/css-multicol-1/

IDL 索引

[Exposed=LayoutWorklet]
interface LayoutChild {
    readonly attribute StylePropertyMapReadOnly styleMap;

    IntrinsicSizesRequest intrinsicSizes();
    LayoutFragmentRequest layoutNextFragment(LayoutConstraints constraints, ChildBreakToken breakToken);
};

[Exposed=LayoutWorklet]
interface LayoutFragment {
    readonly attribute double inlineSize;
    readonly attribute double blockSize;

    attribute double inlineOffset;
    attribute double blockOffset;

    readonly attribute any data;

    readonly attribute ChildBreakToken? breakToken;
};

[Exposed=LayoutWorklet]
interface IntrinsicSizes {
  readonly attribute double minContentSize;
  readonly attribute double maxContentSize;
};

[Constructor(optional LayoutConstraintsOptions options),Exposed=LayoutWorklet]
interface LayoutConstraints {
    readonly attribute double availableInlineSize;
    readonly attribute double availableBlockSize;

    readonly attribute double? fixedInlineSize;
    readonly attribute double? fixedBlockSize;

    readonly attribute double percentageInlineSize;
    readonly attribute double percentageBlockSize;

    readonly attribute double? blockFragmentationOffset;
    readonly attribute BlockFragmentationType blockFragmentationType;

    readonly attribute any data;
};

dictionary LayoutConstraintsOptions {
    double availableInlineSize = 0;
    double availableBlockSize = 0;

    double fixedInlineSize;
    double fixedBlockSize;

    double percentageInlineSize;
    double percentageBlockSize;

    double blockFragmentationOffset;
    BlockFragmentationType blockFragmentationType = "none";

    any data;
};

enum BlockFragmentationType { "none", "page", "column", "region" };

[Exposed=LayoutWorklet]
interface ChildBreakToken {
    readonly attribute BreakType breakType;
    readonly attribute LayoutChild child;
};

[Exposed=LayoutWorklet]
interface BreakToken {
    readonly attribute sequence<ChildBreakToken> childBreakTokens;
    readonly attribute any data;
};

dictionary BreakTokenOptions {
    sequence<ChildBreakToken> childBreakTokens;
    any data = null;
};

enum BreakType { "none", "line", "column", "page", "region" };

[Exposed=LayoutWorklet]
interface LayoutEdgeSizes {
  readonly attribute double inlineStart;
  readonly attribute double inlineEnd;

  readonly attribute double blockStart;
  readonly attribute double blockEnd;

  // Convenience attributes for the sum in one direction.
  readonly attribute double inline;
  readonly attribute double block;
};

[Exposed=LayoutWorklet]
interface LayoutEdges {
  readonly attribute LayoutEdgeSizes border;
  readonly attribute LayoutEdgeSizes scrollbar;
  readonly attribute LayoutEdgeSizes padding;

  readonly attribute LayoutEdgeSizes all;
};

partial interface CSS {
    [SameObject] readonly attribute Worklet layoutWorklet;
};

[Global=(Worklet,LayoutWorklet),Exposed=LayoutWorklet]
interface LayoutWorkletGlobalScope : WorkletGlobalScope {
    void registerLayout(DOMString name, VoidFunction layoutCtor);
};

[Exposed=LayoutWorklet]
dictionary LayoutOptions {
  ChildDisplayType childDisplay = "block";
  LayoutSizingMode sizing = "block-like";
};

[Exposed=LayoutWorklet]
enum ChildDisplayType {
    "block",
    "normal",
};

[Exposed=LayoutWorklet]
enum LayoutSizingMode {
    "block-like",
    "manual",
};

[Exposed=LayoutWorklet]
interface IntrinsicSizesRequest {
};

[Exposed=LayoutWorklet]
interface LayoutFragmentRequest {
};

typedef (IntrinsicSizesRequest or LayoutFragmentRequest)
    LayoutFragmentRequestOrIntrinsicSizesRequest;

// This is the final return value from the author defined layout() method.
dictionary FragmentResultOptions {
    double inlineSize = 0;
    double blockSize = 0;
    double autoBlockSize = 0;
    sequence<LayoutFragment> childFragments = [];
    any data = null;
    BreakTokenOptions breakToken = null;
};

dictionary IntrinsicSizesResultOptions {
    double maxContentSize;
    double minContentSize;
};

问题索引

问题: 目前 API 是一个可迭代的生成器形式。基于实现经验和 Web 开发者经验,这可能会更改为基于 Promise 的 API。每种形式都有其优点和缺点。

Promises

Generator

我们可能希望将 styleMap 存储在 box 上,类似于 layoutInstance
我们可能希望将 styleMap 存储在 box 上,类似于 layoutInstance