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
仕様内インライン
編集者:
(Microsoft)
(Google)
Tab Atkins-Bittner (Google)
(Google)
(Microsoft)

概要

本仕様は、開発者が計算済みスタイルやボックスツリーの変更に応じて ボックスツリーをレイアウトできる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コンテナー

新しいalternative値が <display-inside>生成式に追加されます: layout(<ident>)

layout()
この値は要素にレイアウトAPIコンテナボックスを生成させます。

レイアウトAPIコンテナは、<display-inside>計算値としてlayout()を持つ要素によって生成されるボックスです。

レイアウトAPIコンテナはその内容に対して新しいレイアウトAPI整形コンテキストを確立します。これはブロック整形コンテキストを確立するのと同様ですが、ブロックレイアウトの代わりに著者提供のレイアウトが使用されます。 例えば、フロートはレイアウトAPIコンテナに侵入せず、コンテナのマージンは内容のマージンと折り畳まれません。

レイアウトAPIコンテナの流れの中のすべての子はレイアウトAPI子要素と呼ばれ、著者定義のレイアウトによってレイアウトされます。

レイアウトAPIコンテナは内容に対してブロックコンテナと全く同じように包含ブロックを形成します。[CSS21]

注: 将来の仕様レベルでは包含ブロックの挙動を上書きする方法が追加される可能性があります。

overflowプロパティはレイアウトAPIコンテナに適用されます。詳細は§4.3 オーバーフローで説明します。

レイアウトが完全に著者の裁量であるため、他のレイアウトモード(例:フレックス・ブロック)で使用されるプロパティは適用されない場合があります。例えば、著者が子要素のmarginプロパティを尊重しない場合があります。

2.1. レイアウトAPIコンテナのペインティング

レイアウトAPIコンテナの子要素は、インラインブロック[CSS21]と全く同じように描画されますが、 レイアウトメソッドから返される順序(childFragments経由)が生の文書順の代わりに使用され、 z-indexauto以外の場合、positionstaticでもスタッキングコンテキストが作成されます。

2.2. ボックスツリーの変換

レイアウトAPI子要素は、layout options'childDisplay (クラスのlayoutOptionsで設定)値によって異なる振る舞いをします。

layout options'childDisplay の値が"block"の場合、その子のdisplay値はブロック化されます。これはフレックスコンテナグリッドコンテナの子と似ています。 詳細は[css3-display]を参照してください。

layout options'childDisplay の値が"normal"の場合、ブロック化は行われません。 代わりに、<display-outside>計算値inlineルートインラインボックス)の場合、 LayoutFragmentが各行を表す形で layoutNextFragment()呼び出し時に生成されます。

注: これにより著者は各行の利用可能なインラインサイズを調整し、個別に行を配置できます。

LayoutChildが表すルートインラインボックスの子にも追加の変換があります。

上記いずれの場合も、子要素はアトミックインラインとなります。

注: ユーザーエージェントはブロックレベルボックスに遭遇した際、インライン分割や断片化は行いません。

注: 以下の例では "inline-span" が単一のLayoutChildとして表され、 "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によって生成されたボックス(レイアウト前)を表します。(ボックスはすべてdisplayの計算値がnone以外です)。

LayoutChildは自体にレイアウト情報(インラインサイズやブロックサイズなど)は持たず、LayoutFragment(レイアウト情報を持つ)を生成するために使われます。

著者はこのAPIでLayoutChildを構築することはできません。これはレンダリングエンジンの別の段階(スタイル解決後)で行われます。

LayoutChildは以下によって生成されます:

注: 以下の例は3つの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にまとめられ、レンダリングエンジンが要素間でテキストシェーピングを行えるようにします。

注: 以下の例は1つのLayoutFragmentを生成しますが、 3つの非アトミックインラインから構成されます:
ع<span style="color: blue">ع</span>ع

現在レイアウト中のボックスの子要素を表すLayoutChildの配列がレイアウトメソッドに渡されます。

styleMapは、LayoutChild thisから取得する場合、ユーザーエージェントは以下の手順を実行しなければなりません:
  1. thisStylePropertyMapReadOnly[[styleMap]]内部スロット)を返す。

layoutNextFragment(constraints, breakToken) メソッドがLayoutChild thisで呼び出された場合、ユーザーエージェントは以下の手順を実行しなければなりません:
  1. requestを新しいLayoutFragmentRequestとして生成し、内部スロットを持つ:

  2. requestを返す。

intrinsicSizes()メソッドがLayoutChild thisで呼び出された場合、ユーザーエージェントは以下の手順を実行しなければなりません:
  1. requestを新しいIntrinsicSizesRequestとして生成し、内部スロットを持つ:

  2. requestを返す。

注: layoutNextFragment() および intrinsicSizes() は同期的に実行されません。詳細は§5.5.1 リクエストオブジェクトを参照してください。

3.1.1. LayoutChildrenとボックスツリー

boxは、[[layoutChildMap]]という内部スロットを持ちます。これは、マップであり、LayoutWorkletGlobalScopeからLayoutChildへの対応関係を保持します。

ユーザーエージェントがworkletGlobalScopenameboxを指定してレイアウト子要素を取得したい場合、次の手順を実行しなければなりません:
  1. 以下を確認する:

  2. layoutChildMapbox[[layoutChildMap]]とする。

  3. もしlayoutChildMap[workletGlobalScope]が存在しない場合、次の手順を実行する:

    1. definitionレイアウト定義の取得nameworkletGlobalScopeを渡して得る。

      レイアウト定義の取得が成功し、definition"invalid"でないことを確認する。

    2. childInputPropertiesdefinition子入力プロパティとする。

    3. layoutChildを新しいLayoutChildとして、以下の内部スロットを持たせて生成する:

    4. セット layoutChildMap[workletGlobalScope]にlayoutChildを設定する。

  4. 取得したlayoutChildMap[workletGlobalScope]を返す。

ユーザーエージェントは、boxボックスツリーに挿入された際に、すべてのLayoutWorkletGlobalScopeに対して[[layoutChildMap]]を事前に生成してもよい。

ユーザーエージェントは、boxボックスツリーから削除された際、[[layoutChildMap]]を必ずクリアしなければなりません。

ユーザーエージェントがboxを指定してレイアウト子要素のスタイル更新を行いたい場合、以下の手順を実行しなければなりません:
  1. 以下を確認する:

  2. もしbox包含ブロックレイアウトAPIコンテナでない場合、すべての手順を中止する。

  3. layoutChildMapbox[[layoutChildMap]]とする。

  4. 全てのlayoutChildについてlayoutChildMap内を反復する:

    1. styleMaplayoutChild[[styleMap]]とする。

    2. styleMap宣言boxの新しい計算済みスタイルに基づき更新する。

boxの計算済みスタイルが変更されたとき、ユーザーエージェントはレイアウト子要素のスタイル更新アルゴリズムを実行しなければなりません。

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は、LayoutChildのレイアウト後のCSSフラグメントを表します。これはlayoutNextFragment()メソッドによって生成されます。

LayoutFragmentには、inlineSizeblockSize属性があり、それぞれ子要素のレイアウトアルゴリズムによって設定されます。これらはCSSフラグメントborder boxサイズであり、現在のレイアウトの書字モードに相対的です。

inlineSizeblockSize属性は変更できません。 現在のレイアウトで異なるinlineSizeまたはblockSizeが必要な場合、著者は異なる引数でlayoutNextFragment()を再度実行しなければなりません。

現在のレイアウト内の著者は、生成されたLayoutFragmentinlineOffsetblockOffset属性を設定することで位置を指定できます。著者が設定しない場合はデフォルトでゼロになります。inlineOffsetblockOffset属性は、LayoutFragmentの親のborder boxに対する位置を表し、transformやpositioning(例えばフラグメントが相対配置された場合)を適用する前の値です。

レイアウトアルゴリズムはブロック風のレイアウト(フラグメントをブロック方向に順次配置)を実行し、子要素をインライン方向で中央揃えします。
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はそのトークンチェーンに対してこれ以上LayoutFragmentを生成しません。breakTokenは、layoutNextFragment()関数に渡すことで、特定の子要素の次のLayoutFragmentを生成できます。breakTokenは変更できません。 現在のレイアウトで異なるbreakTokenが必要な場合は、著者は異なる引数でlayoutNextFragment()を再度実行する必要があります。

ユーザーエージェントがgeneratorlayoutFragmentRequestinternalFragmentを指定してレイアウトフラグメントを生成したい場合、次の手順を実行しなければなりません:
  1. targetRealmgeneratorRealmとする。

  2. fragmentを新しいLayoutFragmentとして、以下の設定で生成する:

  3. fragmentを返す。

3.3. 本来サイズ

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

IntrinsicSizesオブジェクトは内部スロットを持ちます:


IntrinsicSizesオブジェクトは、CSSのmin-contentサイズおよびmax-contentサイズを表します。これは、minContentSize属性とmaxContentSize属性を持ち、LayoutChildborder boxのmin/max-content寄与を現在のレイアウトについて表します。これらの属性は、現在のレイアウトの書字方向のインライン方向に対する相対値です。

minContentSizemaxContentSizeは変更できません。 これらは、現在のレイアウトパス内でLayoutChildについて変化しないものとします。

以下は2つの子要素のborder-box本来サイズの例です。
<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) 固定サイズの子。
      childrenSizes[0].maxContentSize; // 400, (380+10+10) 固定サイズの子。

      childrenSizes[1].minContentSize; // 100, "XXXX"のサイズ。
      childrenSizes[1].maxContentSize; // 200, "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 オブジェクトが layout メソッドに渡され、現在のレイアウトがレイアウトを実行するためのすべての制約を表します。また、このオブジェクトは利用可能なスペースの情報を 子レイアウトに渡すためにも使用されます。

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 属性があります。現在のレイアウトは、可能であれば LayoutFragmentblockFragmentationOffset で断片化する必要があります。

現在のレイアウトは、LayoutChildblockFragmentationType に基づき断片化しない選択をすることができます。例えば、子に break-inside: avoid-page; のようなプロパティがある場合です。

ユーザーエージェントが sizingMode、box、および internalLayoutConstraints を指定してレイアウト制約オブジェクトを作成したい場合、次の手順を実行する 必要があります:
  1. sizingMode"block-like" の場合:

    1. fixedInlineSizeboxborder-box インラインサイズ(box の書字モードに対して)を block コンテナと同様に計算した結果とする。

    2. fixedBlockSizeboxブロックサイズauto の場合は null、そうでなければ block コンテナと同様に計算した boxborder-box ブロックサイズ とする。

    3. 次の内容で新しい LayoutConstraints オブジェクトを返す:

      • fixedInlineSizeavailableInlineSizefixedInlineSize に設定。

      • percentageInlineSizeinternalLayoutConstraints のインライン軸(box の書字モードに対して)のパーセンテージ解決サイズに設定。

      • fixedBlockSizefixedBlockSize に設定。

      • availableBlockSize を null でなければ fixedBlockSize に、そうでなければ internalLayoutConstraints のブロック軸(box の書字モードに対して)の 利用可能なスペース に設定。

      • percentageBlockSizeinternalLayoutConstraints のブロック軸(box の書字モードに対して)のパーセンテージ解決サイズに設定。

  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 を生成することができます。 LayoutChild は、blockFragmentationType が none でなければブロック方向に断片化する場合があります。さらに、LayoutChildインラインレベル コンテンツを表す場合、レイアウトオプションchildDisplaylayoutOptions により設定)が "normal" の場合、行ごとに断片化することがあります。

後続の LayoutFragment は、前の LayoutFragmentbreakToken を使用して生成されます。これによって、子レイアウトLayoutFragmentChildBreakToken に記録された地点から生成することができます。

この例では、子フラグメントを特定の行数だけインデントする単純なレイアウトを示しています。

この例はまた、breakToken を用いて LayoutFragment の次のフラグメントを生成する方法を示しています。

さらに、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 {
                // フラグメントに breakToken が無い場合、次の子要素に移る。
                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 オブジェクトは layout メソッドに渡されます。これは、現在レイアウトされているボックスの ボックスモデルのエッジのサイズを表します。

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 (10% * 50px = 5px)
        edges.border.blockEnd; // 2
        edges.scrollbar.inlineEnd; // UA依存、0か16など
        edges.all.block; // 14 (2 + 5 + 5 + 2)
    }
}

4. 他のモジュールとの相互作用

このセクションでは、他の CSS モジュールが CSS Layout API とどのように相互作用するかを説明します。

4.1. サイズ指定

ユーザーエージェントは LayoutConstraints オブジェクトを使って、現在のレイアウトにフラグメントの希望サイズを伝えなければなりません。

ユーザーエージェントがボックスにサイズを強制したい場合、fixedInlineSizefixedBlockSize 属性を使います。

レイアウト API コンテナは、レイアウトオプションsizing の値によって、様々な方法でサイズ情報を受け取ることができます(クラスの layoutOptions で設定)。

レイアウトオプションsizing の値が "block-like" の場合、レイアウト API コンテナに渡される LayoutConstraints は:

レイアウトオプションsizing の値が "manual" の場合、ユーザーエージェントは fixedInlineSizefixedBlockSize を事前に計算してはなりません。 ただし、参加している整形コンテキストによって特定のサイズが強制される場合は例外です。例えば:

注: 下記の例では レイアウト 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 Positioned Layout 3 §8.1 絶対/固定配置非置換要素の幅CSS Positioned Layout 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 は包含ブロック */
    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 Box Alignment 3 §9.1 Determining the Baselines of a Box参照)。ただし、インフロー子要素の順序は、document order ではなく layout メソッド(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 Layout API がユーザーエージェントのレイアウトエンジンとどのように相互作用するかを説明します。

5.1. 概念

レイアウト定義とは、著者が定義したレイアウトについて、LayoutWorkletGlobalScopeが必要とする情報を記述する構造体です(layout()関数で参照できます)。次の要素から成ります:

文書レイアウト定義とは、著者が定義したレイアウトについて、文書が必要とする情報を記述する構造体です(layout()関数で参照できます)。次の要素から成ります:

5.2. レイアウトの無効化

ボックスには、関連付けられたレイアウト有効フラグがあります。これはlayout-validまたはlayout-invalidのいずれかになります。初期値はlayout-invalidです。

ボックスには、関連付けられた内在サイズ有効フラグがあります。これはintrinsic-sizes-validまたはintrinsic-sizes-invalidのいずれかになります。初期値はintrinsic-sizes-invalidです。

ユーザーエージェントが レイアウト関数を無効化する場合(boxを指定)、ユーザーエージェントは以下の手順を必ず実行します:
  1. layoutFunctionbox の計算スタイルの display プロパティの layout() 関数とする(存在する場合)。他の型の値(例:grid)の場合は、これらの手順を中止する。

  2. namelayoutFunction の最初の引数とする。

  3. documentDefinition文書レイアウト定義を取得nameを指定)した結果とする。

    文書レイアウト定義を取得が失敗した場合、またはdocumentDefinition"invalid" の場合は、これらの手順を中止する。

  4. inputPropertiesdocumentDefinition入力プロパティとする。

  5. childInputPropertiesdocumentDefinition子入力プロパティとする。

  6. inputProperties の各 property について、property計算値が変化していれば、レイアウト有効フラグboxに対してlayout-invalidに設定し、内在サイズ有効フラグintrinsic-sizes-invalidに設定する。

  7. childInputProperties の各 property について、property計算値が変化していれば、レイアウト有効フラグboxに対してlayout-invalidに設定し、内在サイズ有効フラグintrinsic-sizes-invalidに設定する。

レイアウト関数の無効化は、ユーザーエージェントがボックスの計算スタイルを再計算したとき、またはそのボックスの子の計算スタイルが再計算されたときに必ず実行されます。

LayoutChildで表現される子ボックスボックスツリーに追加・削除された場合、または計算スタイルの変更や子孫の変更によりレイアウトが無効化された場合、かつその無効化がボックスツリー上位に伝播すべき場合、現在のレイアウト有効フラグlayout-invalidに、現在の内在サイズ有効フラグintrinsic-sizes-invalidに設定します。

レイアウト API コンテナの計算スタイルが変更され、その変更がLayoutEdgesオブジェクト内部の値に影響する場合、ボックスのレイアウト有効フラグlayout-invalidに、ボックスの内在サイズ有効フラグintrinsic-sizes-invalidに設定します。

計算スタイルの変更がLayoutConstraintsオブジェクト内部の値に影響する場合は、ボックスの内在サイズ有効フラグのみintrinsic-sizes-invalidに設定します。

注: LayoutConstraintsオブジェクトはレイアウト関数だけに渡されるので、内在サイズを無効化する必要はありません。

注: 例として、以下のプロパティはLayoutEdgesオブジェクトを変更する可能性があります:

また、以下のプロパティはLayoutConstraintsオブジェクトを変更する可能性があります:

注: これは CSS Layout API に関連するレイアウト無効化のみを説明しています。すべてのボックスは概念的にレイアウト有効フラグを持ち、これらの変更はボックスツリーを通して伝播します。

5.3. レイアウトワークレット

layoutWorklet属性は、レイアウトに関連するすべてのクラスを管理するWorkletへアクセスすることができます。

layoutWorkletワークレットグローバルスコープ型LayoutWorkletGlobalScopeです。

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

LayoutWorkletGlobalScopeは、layoutWorkletのグローバル実行コンテキストです。

[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",
};

documentmap 型の 文書レイアウト定義 を持ちます。 最初はこの map は空ですが、registerLayout(name, layoutCtor) が呼び出されたときに値が追加されます。

LayoutWorkletGlobalScopemap 型の レイアウト定義 を持ちます。 最初はこの map は空ですが、registerLayout(name, layoutCtor) が呼び出されたときに値が追加されます。

box のうち layout API container に該当するものは、 map 型の レイアウトクラスインスタンス を持ちます。最初はこの map は空ですが、ユーザーエージェントが 内在サイズの決定 または フラグメント生成 を行う際に値が追加されます。

registerLayout(name, layoutCtor) メソッドが呼び出されたとき、ユーザーエージェントは以下の手順を必ず実行します:
  1. name が空文字列なら、throwTypeError を投げて、以降の手順を中止する。

  2. layoutDefinitionMapLayoutWorkletGlobalScopeレイアウト定義 map とする。

  3. layoutDefinitionMap[name] が 存在する場合、throw で "InvalidModificationError" DOMException を投げて、以降の手順を中止する。

  4. inputProperties を空の sequence<DOMString> とする。

  5. inputPropertiesIterableGet(layoutCtor, "inputProperties") の結果とする。

  6. inputPropertiesIterable が undefined でなければ、inputProperties変換した値(sequence<DOMString>)を格納する。例外が thrown された場合は例外を投げて以降の手順を中止する。

    注: input properties getter が返す CSS プロパティのリストは、カスタムもしくはネイティブ CSS プロパティどちらでもよい。

    注: CSS プロパティのリストにはショートハンドも含まれる可能性がある。

    注: レイアウトクラスを将来互換にするには、ユーザーエージェントで現在無効な CSS プロパティも含めることができる(例:margin-bikeshed-property)。

  7. childInputProperties を空の sequence<DOMString> とする。

  8. childInputPropertiesIterableGet(layoutCtor, "childInputProperties") の結果とする。

  9. childInputPropertiesIterable が undefined でなければ、childInputProperties変換した値(sequence<DOMString>)を格納する。例外が thrown された場合は例外を投げて以降の手順を中止する。

  10. layoutOptionsValueGet(layoutCtor, "layoutOptions") の結果とする。

  11. layoutOptions変換した LayoutOptions とする。 例外が thrown された場合は例外を投げて以降の手順を中止する。

  12. IsConstructor(layoutCtor) が false なら、throwTypeError を投げて以降の手順を中止する。

  13. prototypeGet(layoutCtor, "prototype") の結果とする。

  14. Type(prototype) が Object でないなら throwTypeError を投げて以降の手順を中止する。

  15. intrinsicSizesGet(prototype, "intrinsicSizes") の結果とする。

  16. IsCallable(intrinsicSizes) が false なら throwTypeError を投げて以降の手順を中止する。

  17. intrinsicSizes[[FunctionKind]] 内部スロットが "generator" でないなら throwTypeError を投げて以降の手順を中止する。

  18. layoutGet(prototype, "layout") の結果とする。

  19. IsCallable(layout) が false なら throwTypeError を投げて以降の手順を中止する。

  20. layout[[FunctionKind]] 内部スロットが "generator" でないなら throwTypeError を投げて以降の手順を中止する。

  21. definition を新しい レイアウト定義として以下の内容で生成する:

  22. Set layoutDefinitionMap[name] に definition を設定する。

  23. Queue a task で以下を実行:

    1. documentLayoutDefinitionMap を関連づけられた document文書レイアウト定義 map とする。

    2. documentDefinition を新しい 文書レイアウト定義として以下の内容で生成する:

    3. documentLayoutDefinitionMap[name] が 存在する場合、以下を実行:

      1. existingDocumentDefinitionget(documentLayoutDefinitionMap[name]) の結果とする。

      2. existingDocumentDefinition"invalid" なら以降の手順を中止する。

      3. existingDocumentDefinitiondocumentDefinition が等価でない場合 (入力プロパティ子入力プロパティレイアウトオプション が異なる場合)、次を行う:

        Set documentLayoutDefinitionMap[name] に "invalid" を設定する。

        同じクラスが異なる inputPropertieschildInputPropertieslayoutOptions で登録された旨のエラーをデバッグコンソールに出力する。

    4. それ以外の場合は、set 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) {
        // 内在サイズの処理。
    }

    *layout(children, edges, constraints, styleMap, breakToken) {
        // レイアウト処理。
    }
}

5.5. レイアウトエンジン

Issue: 現在 API は generator 形式ですが、実装経験と web 開発者の意見によって、promise ベースの API に変更される可能性があります。どちらにも利点と欠点があります。

Promises

Generator

5.5.1. リクエストオブジェクト

[Exposed=LayoutWorklet]
interface IntrinsicSizesRequest {
};

[Exposed=LayoutWorklet]
interface LayoutFragmentRequest {
};

typedef (IntrinsicSizesRequest or LayoutFragmentRequest)
    LayoutFragmentRequestOrIntrinsicSizesRequest;

IntrinsicSizesRequest は内部スロット:

LayoutFragmentRequest は内部スロット:


著者定義の layout メソッドと intrinsic sizes メソッドは、通常の javascript 関数ではなく generator 関数となっています。これはユーザーエージェントが非同期・並列レイアウトエンジンをサポートするためです。

著者が layoutNextFragment()LayoutChild に呼び出すと、ユーザーエージェントは LayoutFragment を同期的に生成して返すのではなく、LayoutFragmentRequest を返します。これは著者には完全に不透明なオブジェクトですが、内部スロットとして layoutNextFragment() の呼び出し情報を持ちます。

LayoutFragmentRequest がレイアウトジェネレーターオブジェクトから yield された場合、ユーザーエージェントのレイアウトエンジンは他の処理と並行して、または別スレッドでこのアルゴリズムを非同期に実行することがあります。エンジンが LayoutFragment を生成したら、その結果でジェネレーターオブジェクトを「tick」します。

intrinsicSizes() メソッドも同様です。

以下は javascript で書かれたレイアウトエンジンの例です。
class LayoutEngine {
  // rootBox、LayoutConstraints、BreakToken を受け取って LayoutFragment を生成する。
  layoutEntry(rootBox, rootPageConstraints, breakToken) {
    return layoutFragment({
      box: rootBox,
      layoutConstraints: rootPageConstraints,
      breakToken: breakToken,
    });
  }

  // LayoutFragmentRequest を受け取り、対応するレイアウトアルゴリズムを呼び出して 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) {
      // UA はフラグメント生成を並列(別スレッド)で行うこともできる。例は同期処理のみ。
      let fragments = nextFragmentRequest.value.map(layoutFragment);

      // UA は他の処理(GC等)で yield することもできる。例は同期で進める。
      nextFragmentRequest = fragmentRequestGenerator.next(fragments);
    }

    return nextFragmentRequest.value; // 最終的な LayoutFragment を返す。
  }
}

5.6. レイアウトの実行

// 著者定義 layout() メソッドの最終返却値。
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. 内在サイズの決定

内在サイズの決定アルゴリズムは、ユーザーエージェントが著者定義レイアウトから box内在サイズ情報を取得する方法を定義します。

注: 内在サイズの決定アルゴリズムは、ユーザーエージェントが過去の呼び出し結果を任意の数キャッシュして再利用できるようにしています。

ユーザーエージェントが、与えられた box, childBoxesLayout API フォーマッティングコンテキスト内在サイズの決定をしたい場合、以下の手順を必ず実行します:
  1. layoutFunctionboxlayout()計算値<display-inside>)とする。

  2. layoutFunction内在サイズ有効フラグintrinsic-sizes-valid なら、ユーザーエージェントは前回の内在サイズ値を再利用してもよい。その場合は以降の手順を中止してキャッシュ値を使う。

  3. layoutFunction内在サイズ有効フラグintrinsic-sizes-valid に設定する。

  4. namelayoutFunction の第1引数とする。

  5. documentDefinition文書レイアウト定義取得name指定)した結果とする。

    もし 文書レイアウト定義取得 が失敗、または documentDefinition"invalid" なら、 boxflow layout にフォールバックし、以降の手順を中止。

  6. workletGlobalScope をレイアウト LayoutWorkletGlobalScopeWorkletGlobalScopesリストから取得)とする。

    ユーザーエージェントは、リストに少なくとも2つの LayoutWorkletGlobalScope を持ち、選択しなければならない(メモリ制約下を除く)。

    注: グローバルオブジェクトやクラス上の再生成不可能な状態に依存しないための仕様です。

    また、ユーザーエージェントはこのタイミングでレイアウト Workletに対して WorkletGlobalScope を作成してもよい。

  7. 内在サイズコールバック呼び出しname, box, childBoxes, workletGlobalScope、必要に応じて並列で実行)を実行する。

    注: 並列実行する場合、そのスレッドで使える layout worklet global scope を選ぶべき。

ユーザーエージェントが 内在サイズコールバック呼び出しname, box, childBoxes, workletGlobalScope)をしたい場合、以下の手順を必ず実行します:
  1. definitionレイアウト定義取得name, workletGlobalScope)の結果とする。

    失敗なら boxflow layout にフォールバックし、以降中止。

  2. layoutInstanceレイアウトクラスインスタンス取得box, definition, workletGlobalScope)の結果とする。

    失敗なら boxflow layout にフォールバックし、以降中止。

  3. inputPropertiesdefinition入力プロパティとする。

  4. children を新しいリストとする。

  5. childBox について:

    1. layoutChildレイアウト子取得workletGlobalScope, name, childBox)の結果とする。

    2. Append layoutChildchildren に追加。

  6. edgesLayoutEdgeSizesbox計算値をもとに生成)とする。

  7. styleMapStylePropertyMapReadOnlyboxinputPropertiesにあるプロパティのみの計算値で生成)とする。

    styleMapboxに持たせるべきかも。

  8. この段階で、childrenstyleMapが前回呼び出しと同じなら、キャッシュされた内在サイズを利用して以降の手順を中止してもよい。

  9. intrinsicSizesGeneratorFunctiondefinition内在サイズジェネレーター関数とする。

  10. intrinsicSizesGeneratorInvoke(intrinsicSizesGeneratorFunction, layoutInstance, «children, edges, styleMap»)の結果とする。

    例外発生時は boxflow layout にフォールバックし、以降中止。

  11. intrinsicSizesValuerun a generatorintrinsicSizesGenerator, "intrinsic-sizes")の結果とする。

    失敗時は boxflow layout にフォールバックし、以降中止。

  12. intrinsicSizes変換intrinsicSizesValueIntrinsicSizesResultOptionsへ)したものとする。 例外時は boxflow layout にフォールバックし、以降中止。

  13. box内在サイズを次のように設定:

5.6.2. フラグメント生成

フラグメント生成アルゴリズムは、ユーザーエージェントが著者定義レイアウト用の boxfragment を生成する方法を定義します。

注: フラグメント生成アルゴリズムも、ユーザーエージェントが過去の呼び出し結果を任意の数キャッシュして再利用できるようになっています。

ユーザーエージェントが、与えられた box, childBoxes, internalLayoutConstraints, (任意の)internalBreakTokenLayout API フォーマッティングコンテキストフラグメント生成をしたい場合、以下の手順を必ず実行します:
  1. layoutFunctionboxlayout()計算値<display-inside>)とする。

  2. layoutFunctionレイアウト有効フラグlayout-valid なら、ユーザーエージェントは前回の値を再利用してもよい。その場合は以降の手順を中止してキャッシュ値を使う。

  3. layoutFunctionレイアウト有効フラグlayout-valid に設定する。

  4. namelayoutFunction の第1引数とする。

  5. documentDefinition文書レイアウト定義取得name指定)した結果とする。

    失敗、または documentDefinition"invalid" なら、boxflow layout にフォールバックし、以降中止。

  6. workletGlobalScope をレイアウト LayoutWorkletGlobalScopeWorkletGlobalScopesリストから取得)とする。

    ユーザーエージェントは、リストに少なくとも2つの LayoutWorkletGlobalScope を持ち、選択しなければならない(メモリ制約下を除く)。

    注: グローバルオブジェクトやクラス上の再生成不可能な状態に依存しないための仕様です。

    また、ユーザーエージェントはこのタイミングでレイアウト Workletに対して WorkletGlobalScope を作成してもよい。

  7. レイアウトコールバック呼び出しname, box, childBoxes, internalLayoutConstraints, internalBreakToken, workletGlobalScope、必要に応じて並列で実行)を実行する。

    注: 並列実行する場合、そのスレッドで使える layout worklet global scope を選ぶべき。

ユーザーエージェントが レイアウトコールバック呼び出しname, box, childBoxes, internalLayoutConstraints, internalBreakToken, workletGlobalScope)をしたい場合、以下の手順を必ず実行します:
  1. definitionレイアウト定義取得name, workletGlobalScope)の結果とする。

    失敗なら boxflow layout にフォールバックし、以降中止。

  2. layoutInstanceレイアウトクラスインスタンス取得box, definition, workletGlobalScope)の結果とする。

    失敗なら boxflow layout にフォールバックし、以降中止。

  3. sizingModedefinitionlayout optionssizing プロパティとする。

  4. inputPropertiesdefinition入力プロパティとする。

  5. children を新しいリストとする。

  6. childBox について:

    1. layoutChildレイアウト子取得workletGlobalScope, name, childBox)の結果とする。

    2. Append layoutChildchildren に追加。

  7. edgesLayoutEdgeSizesbox計算値をもとに生成)とする。

  8. layoutConstraintsレイアウト制約オブジェクト生成internalLayoutConstraints, box, sizingMode)の結果とする。

  9. styleMapStylePropertyMapReadOnlyboxinputPropertiesにあるプロパティのみの計算値で生成)とする。

    styleMapboxに持たせるべきかも。

  10. breakTokenBreakTokeninternalBreakTokenから適切な情報で生成)とする。

    もし internalBreakToken が null なら breakToken も null。

  11. この段階で、children, styleMap, layoutConstraints, breakToken が前回呼び出しと同じなら、キャッシュされたフラグメントを利用して以降の手順を中止してもよい。

  12. layoutGeneratorFunctiondefinitionレイアウトジェネレーター関数とする。

  13. layoutGeneratorInvoke(layoutGeneratorFunction, layoutInstance, «children, edges, layoutConstraints, styleMap, breakToken»)の結果とする。

    例外発生時は boxflow layout にフォールバックし、以降中止。

  14. fragmentValuerun a generatorlayoutGenerator, "layout")の結果とする。

    失敗時は boxflow layout にフォールバックし、以降中止。

  15. fragment変換fragmentValueFragmentResultOptionsへ)したものとする。 例外時は boxflow layout にフォールバックし、以降中止。

  16. childFragmentfragmentchildFragments)について次を実行:

    1. childFragment[[generator]] 内部スロットが layoutGenerator と異なる場合、 boxflow layout にフォールバックし、以降中止。

  17. sizingMode"block-like" の場合:

    • 次の通り:

      1. inlineSizelayoutConstraintsfixedInlineSize(必須)とする。

      2. blockSizeboxborder-box block size(書字モードに対し、fragmentautoBlockSize を「内在高さ」として)とする。

    • それ以外(sizingMode"manual"):

      1. inlineSizefragmentinlineSize とする。

      2. blockSizefragmentblockSize とする。

  18. boxfragment を次の内容で返す:

5.6.3. ユーティリティアルゴリズム

このセクションでは、内在サイズの決定アルゴリズムとフラグメント生成アルゴリズムで共通して使われるアルゴリズムを示します。

ユーザーエージェントが文書レイアウト定義取得nameで行いたいとき、以下の手順を必ず実行します:
  1. documentLayoutDefinitionMapを、関連するdocument文書レイアウト定義mapとする。

  2. documentLayoutDefinitionMap[name]が存在しない場合、失敗を返して以降の手順を中止する。

  3. get documentLayoutDefinitionMap[name]の結果を返す。

ユーザーエージェントがレイアウト定義取得nameworkletGlobalScopeで行いたいとき、以下の手順を必ず実行します:
  1. layoutDefinitionMapworkletGlobalScopeレイアウト定義mapとする。

  2. layoutDefinitionMap[name]が存在しない場合、以下を実行:

    1. Queue a taskで以下を実行:

      1. documentLayoutDefinitionMapを関連するdocument文書レイアウト定義mapとする。

      2. Set documentLayoutDefinitionMap[name]に"invalid"を設定する。

      3. ユーザーエージェントは、すべてのLayoutWorkletGlobalScopeでクラスが登録されていない旨のエラーをデバッグコンソールに推奨で出力するべきです。

    2. 失敗を返して、以降の手順を中止する。

  3. get layoutDefinitionMap[name]の結果を返す。

ユーザーエージェントがレイアウトクラスインスタンス取得boxdefinitionworkletGlobalScopeで行いたいとき、以下の手順を必ず実行します:
  1. layoutClassInstanceMapboxレイアウトクラスインスタンスmapとする。

  2. layoutInstanceget layoutClassInstanceMap[workletGlobalScope]の結果とする。layoutInstanceがnullの場合、以下を実行:

    1. definitionコンストラクター有効フラグがfalseの場合、失敗を返して以降の手順を中止する。

    2. layoutCtordefinitionクラスコンストラクターとする。

    3. layoutInstanceConstruct(layoutCtor)の結果とする。

      constructが例外を投げた場合はdefinitionコンストラクター有効フラグをfalseに設定し、失敗を返して以降の手順を中止する。

    4. Set layoutClassInstanceMap[workletGlobalScope]にlayoutInstanceを設定する。

  3. layoutInstanceを返す。

ユーザーエージェントがジェネレーター実行generatorgeneratorTypeで行いたいとき、以下の手順を必ず実行します:
  1. donefalseで初期化したbooleanとする。

  2. nextValueをundefinedとする。

  3. donetrueになるまで以下を繰り返す:

    1. nextFunctionGet(generator, "next")の結果とする。

    2. IsCallable(nextFunction)がfalseならthrowTypeErrorを投げて以降の手順を中止する。

    3. nextResultInvoke(nextFunction, generator, «nextValue»)の結果とする。

      例外発生時は失敗を返して以降の手順を中止する。

    4. Type(nextResult)がObjectでない場合はthrowTypeErrorを投げて以降の手順を中止する。

    5. requestOrRequestsGet(nextResult, "value")の結果とする。

    6. doneGet(nextResult, "done")の結果とする。

    7. GetMethod(requestOrRequests, @@iterable)が存在する場合:

      1. resultsリストとする。

      2. requests変換(requestOrRequestssequence<LayoutFragmentRequestOrIntrinsicSizesRequest>へ)したものとする。

        例外発生時は例外を投げて以降の手順を中止する。

      3. request について:

        1. resultジェネレーター結果生成(request, generator, generatorType)したものとする。

        2. Append resultresultsに追加。

      4. nextValueresultsに設定する。

      5. 続行。

    8. request変換(requestOrRequestsLayoutFragmentRequestOrIntrinsicSizesRequestへ)したものとする。

      例外発生時は例外を投げて以降の手順を中止する。

    9. resultジェネレーター結果生成(request, generator, generatorType)したものとする。

      ジェネレーター結果生成が失敗を返した場合、失敗を返して以降の手順を中止する。

    10. nextValueresultに設定する。

    11. 続行。

    ユーザーエージェントは上記ループを順不同・並列で実行してもよい。ただしrequestsresultsの順序は必ず一貫性が必要。

    注: これはユーザーエージェントが適切なレイアウトアルゴリズムを別スレッドや非同期(例えば他の処理とタイムスライス)で実行可能にするためです。並列実行の場合、外側ループは全スレッドの完了を待ってからジェネレーターを再度呼び出す必要があります。著者に部分結果を返してはいけません。

  4. Get(nextResult, "value")の結果を返す。

ユーザーエージェントがジェネレーター結果生成requestgeneratorgeneratorTypeで行いたいとき、以下の手順を必ず実行します:
  1. requestIntrinsicSizesRequestなら:

    1. layoutChildrequestの内部スロット[[layoutChild]]から取得。

    2. boxlayoutChildの内部スロット[[box]]から取得。

    3. boxbox tree接続されていないなら、失敗を返して中止する。

      注: 著者は前の呼び出しからLayoutChildを保持する場合がありますが、boxは一度だけbox-treeに接続され、再利用されないことが想定されています。

    4. internalIntrinsicSizesをユーザーエージェントがboxborder boxのmin/max content contributionを計算した結果とする。

    5. 内在サイズオブジェクト生成(request, internalIntrinsicSizes)の結果を返す。

  2. requestLayoutFragmentRequestかつgeneratorType"layout"なら:

    1. layoutChildrequestの内部スロット[[layoutChild]]から取得。

    2. boxlayoutChildの内部スロット[[box]]から取得。

    3. boxbox tree接続されていないなら、失敗を返して中止する。

      注: 著者は前の呼び出しからLayoutChildを保持する場合がありますが、boxは一度だけbox-treeに接続され、再利用されないことが想定されています。

    4. childLayoutConstraintsrequestの内部スロット[[layoutConstraints]]から取得。

    5. childBreakTokenrequestの内部スロット[[breakToken]]から取得。

    6. internalFragmentをユーザーエージェントがboxchildLayoutConstraintschildBreakTokenから生成したfragmentとする。

    7. レイアウトフラグメント生成(generator, request, internalFragment)の結果を返す。

  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">で他の規範的な本文と区別されます。例えば: UAは必ずアクセシブルな代替手段を提供しなければなりません。

適合性クラス

本仕様への適合性は、次の三つの適合性クラスで定義されます:

スタイルシート
CSS スタイルシート
レンダラー
UAで、スタイルシートの意味を解釈し、それを用いる文書をレンダリングするもの。
オーサリングツール
UAで、スタイルシートを作成するもの。

本モジュールで定義された構文を用いる全ての文が、汎用CSS文法および各機能の個別文法に従って有効である場合、そのスタイルシートは本仕様に適合しているとみなされます。

レンダラーは、スタイルシートを適切な仕様通りに解釈することに加えて、本仕様で定義された全ての機能を正しく解析し、文書を適切にレンダリングすることで本仕様に適合しているとみなされます。ただし、デバイスの制限によりUAが文書を正しくレンダリングできない場合でも、そのUAは非適合とはなりません。(例えば、UAは白黒モニター上で色を表示する必要はありません。)

オーサリングツールは、汎用CSS文法および本モジュールの各機能ごとの文法に従い構文的に正しいスタイルシートを書き、さらに本モジュールで記載されたスタイルシートの全ての適合性要件を満たしている場合、本仕様に適合しています。

部分実装

著者が将来互換性のあるパース規則を活用してフォールバック値を指定できるよう、CSSレンダラーは必ず、有効なレベルのサポートがない at-規則、プロパティ、プロパティ値、キーワード、その他の構文を無効として(必要に応じて無視)扱う必要があります。特に、ユーザーエージェントは絶対に、単一の複数値プロパティ宣言で未サポートの値だけを無視してサポートされている値を適用することをしてはなりません。値の一部が無効とみなされる場合(未サポート値は必ず無効とみなされる)、CSSでは宣言全体を無視する必要があります。

不安定及び独自機能の実装について

将来の安定したCSS機能との衝突を避けるため、CSSWGはベストプラクティスに従った不安定機能および独自拡張機能の実装を推奨します。

非実験的実装

仕様がCandidate Recommendationの段階に到達すると、非実験的な実装が可能となり、実装者は仕様通りに正しく実装できるCRレベルの機能については、ベンダープレフィックスなしの実装をリリースすべきです。

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 Fragmentation Module Level 3. 2017年2月9日. CR. URL: https://www.w3.org/TR/css-break-3/
[CSS-CASCADE-4]
Elika Etemad; Tab Atkins Jr.. CSS Cascading and Inheritance Level 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 Flexible Box Layout Module Level 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 Grid Layout Module Level 1. 2017年12月14日. CR. URL: https://www.w3.org/TR/css-grid-1/
[CSS-INLINE-3]
Dave Cramer; Elika Etemad; Steve Zilles. CSS Inline Layout Module Level 3. 2016年5月24日. WD. URL: https://www.w3.org/TR/css-inline-3/
[CSS-OVERFLOW-3]
David Baron; Florian Rivoal. CSS Overflow Module Level 3. 2016年5月31日. WD. URL: https://www.w3.org/TR/css-overflow-3/
[CSS-PAGE-FLOATS-3]
Johannes Wilm. CSS Page Floats. 2015年9月15日. WD. URL: https://www.w3.org/TR/css-page-floats-3/
[CSS-POSITION-3]
Rossen Atanassov; Arron Eicholz. CSS Positioned Layout Module Level 3. 2016年5月17日. WD. URL: https://www.w3.org/TR/css-position-3/
[CSS-PSEUDO-4]
Daniel Glazman; Elika Etemad; Alan Stearns. CSS Pseudo-Elements Module Level 4. 2016年6月7日. WD. URL: https://www.w3.org/TR/css-pseudo-4/
[CSS-RUBY-1]
Elika Etemad; Koji Ishii. CSS Ruby Layout Module Level 1. 2014年8月5日. WD. URL: https://www.w3.org/TR/css-ruby-1/
[CSS-SIZING-3]
Tab Atkins Jr.; Elika Etemad. CSS Intrinsic & Extrinsic Sizing Module Level 3. 2018年3月4日. WD. URL: https://www.w3.org/TR/css-sizing-3/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS Syntax Module Level 3. 2014年2月20日. CR. URL: https://www.w3.org/TR/css-syntax-3/
[CSS-TYPED-OM-1]
Shane Stephens; Tab Atkins Jr.. CSS Typed OM Level 1. 2017年8月1日. WD. URL: https://www.w3.org/TR/css-typed-om-1/
[CSS-VALUES-3]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 3. 2016年9月29日. CR. URL: https://www.w3.org/TR/css-values-3/
[CSS-WRITING-MODES-3]
Elika Etemad; Koji Ishii. CSS Writing Modes Level 3. 2017年12月7日. CR. URL: https://www.w3.org/TR/css-writing-modes-3/
[CSS21]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. 2011年6月7日. REC. URL: https://www.w3.org/TR/CSS2/
[CSS22]
Bert Bos. Cascading Style Sheets Level 2 Revision 2 (CSS 2.2) Specification. 2016年4月12日. WD. URL: https://www.w3.org/TR/CSS22/
[CSS3-DISPLAY]
Elika Etemad. CSS Display Module Level 3. 2017年7月20日. WD. URL: https://www.w3.org/TR/css-display-3/
[CSSOM-1]
Simon Pieters; Glenn Adams. CSS Object Model (CSSOM). 2016年3月17日. WD. URL: https://www.w3.org/TR/cssom-1/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. 1997年3月. Best Current Practice. 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. Worklets Level 1. 2016年6月7日. WD. URL: https://www.w3.org/TR/worklets-1/

参考情報

[CSS-BACKGROUNDS-3]
Bert Bos; Elika Etemad; Brad Kemper. CSS Backgrounds and Borders Module Level 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 Multi-column Layout Module Level 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へ変更される可能性があります。それぞれに長所と短所があります。

Promise

ジェネレーター

styleMapboxに保持するべきかもしれません。layoutInstanceのように。
styleMapboxに保持するべきかもしれません。layoutInstanceのように。