CSS Layout API Level 1

A Collection of Interesting Ideas,

This version:
https://drafts.css-houdini.org/css-layout-api-1/
Feedback:
public-houdini@w3.org with subject line “[css-layout-api] … message topic …” (archives)
Issue Tracking:
GitHub
Inline In Spec
Editors:
Tab Atkins-Bittner (Google)

Abstract

Status of this document

1. Introduction

This section is not normative.

The layout stage of CSS is responsible for generating and positioning fragments from the box tree.

This specification describes an API which allows developers to layout a box in response to computed style and box tree changes.

2. Layout API Containers

Name: <display-inside>
New values: layout(<ident>)
layout()
This value causes an element to generate a layout API container box.

A layout API container is the box generated by an element with a <display-inside> computed value layout().

A layout API container establishes a new layout API formatting context for its contents. This is the same as establishing a block formatting context, except that the layout provided by the author is used instead of the block layout. For example, floats do not intrude into the layout API container, and the layout API container’s margins do not collapse with the margins of its contents.

All inflow children of a layout API container are called layout API children and are laid out using the auther defined layout.

Layout API containers form a containing block for their contents exactly like block containers do. [CSS21]

Note: In a future level of the specification there may be a way to override the containing block behaviour.

The overflow property applies to layout API containers. This is discussed in §4.3 Overflow.

As the layout is entirely up to the author, properties which are used in other layout modes (e.g. flex, block) may not apply. For example an author may not repect the margin property on children.

2.1. Layout API Container Painting

Layout API Container children paint exactly the same as inline blocks [CSS21], except that the order in which they are returned from the layout method (via childFragments) is used in place of raw document order, and z-index values other than auto create a stacking context even if position is static.

2.2. Box Tree Transformations

The layout API children can act in different ways depending on the value of child display (set by childDisplay on the class).

If the value of child display is "block" the display value of that child is blockified. This is similar to children of flex containers or grid containers. See [css3-display].

If the value of child display is "normal", no blockification occurs. Instead children with a <display-outside> computed value of inline (a root inline box) will produce a single Fragment representing each line when layoutNextFragment() is called.

Note: This allows authors to adjust the available inline size of each line, and position each line separately.

Children of a LayoutChild which represents root inline box also have some additional transformations.

In both of the above cases the children become atomic inlines.

Note: User agents would not perform any "inline splitting" or fragmenting when they encounter a block-level box.

Note: In the example below "inline-span" would be represented as a single LayoutChild with both "block" and "float" being atomic inlines.
<span id="inline-span">
  Text
  <div id="block"></div>
  <div id="float"></div>
  Text
</span>

3. Layout API Model and Terminology

This section gives an overview of the Layout API given to authors.

The current layout is the layout algorithm for the box we are currently performing layout for.

The parent layout is the layout algorithm for the box’s direct parent, (the layout algorithm which is requesting the current layout to be performed).

A child layout is the layout algorithm for a LayoutChild of the current layout.

3.1. Layout Children

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

    IntrinsicSizesRequest intrinsicSizes();
    FragmentRequest layoutNextFragment(ConstraintSpace space, ChildBreakToken breakToken);
};

A LayoutChild represents either a CSS generated box before layout has occured. (The box or boxes will all have a computed value of display that is not none).

The LayoutChild does not contain any layout information itself (like inline or block size) but can be used to generate Fragments which do contain layout information.

An author cannot construct a LayoutChild with this API, this happens at a separate stage of the rendering engine (post style resolution).

The LayoutChild has computed style which can be accessed by styleMap. The styleMap will only contain properties which are listed in the child input properties array.

A LayoutChild could be generated by:

Note: As an example the following would be placed into three LayoutChildren:
        <style>
          #box::before { content: 'hello!'; }
        </style>
Note: As an example the following would be placed into a single LayoutChild as they share a root inline box:
This is a next node, <span>with some additional styling,
that may</span> break over<br>multiple lines.

Multiple non-atomic inlines are placed within the same LayoutChild to allow rendering engines to perform text shaping across element boundaries.

Note: As an example the following should produce one Fragment but is from three non-atomic inlines:
ع<span style="color: blue">ع</span>ع

Note: In a future level of the specification there may be a way to query the computed style of inline boxes inside a root inline box represented by a LayoutChild.

An array of LayoutChildren is passed into the layout method which represents the children of the current box which is being laid out.

To perform layout on a box the author can invoke the layoutNextFragment() method. This will produce a Fragment which contains layout information.

The layoutNextFragment() method may be invoked multiple times with different arguments to query the LayoutChild for different layout information.

3.2. Layout Fragments

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

    attribute double inlineOffset;
    attribute double blockOffset;

    readonly attribute any data;

    readonly attribute ChildBreakToken? breakToken;
};

/* NOTE EVERYTHING BELOW HERE IS SPECULATIVE */
interface LineFragment : Fragment {
    readonly attribute sequence<LineFragmentSegment> segments;
};

interface LineFragmentSegment {
    readonly attribute LineFragmentSegmentType type;
    readonly attribute LineFragmentSegmentBreakType breakType;
    readonly attribute FontMetrics? metrics;
    readonly attribute double inlineOffset;
};

enum LineFragmentSegmentType {
    "word-break",
    "atomic-inline",
    /* TODO add others here, not sure yet. */
};

enum LineFragmentSegmentBreakType {
    "something"
    /* TODO add others here, not sure yet. */
};

A Fragment represents a CSS fragment of a LayoutChild after layout has occurred on that child. This is produced by the layoutNextFragment() method.

The Fragment has inlineSize and blockSize attributes, which are set by the respective child’s layout algorithm. They cannot be changed. If the current layout requires a different inlineSize or blockSize the author must perform layoutNextFragment() again with different arguments in order to get different results.

The author inside the current layout can position a resulting Fragment by setting its inlineOffset and blockOffset attributes. If not set by the author they default to zero. The inlineOffset and blockOffset attributes represent the position of the Fragment relative to its parent’s border box, before transform or positioning (e.g. if a fragment is relatively positioned) has been applied.

The layout algorithm performs a block-like layout (positioning fragments sequentially in the block direction), while centering its children in the inline direction.
registerLayout('block-like', class {
    *intrinsicSizes(styleMap, children) {
      const childrenSizes = yield children.map((child) => {
          return child.intrinsicSizes();
      });

      const maxContentSize = childrenSizes.reduce((max, childSizes) => {
          return Math.max(max, childSizes.maxContentContribution);
      }, 0);

      const minContentSize = childrenSizes.reduce((max, childSizes) => {
          return Math.max(max, childSizes.minContentContribution);
      }, 0);

      return {maxContentSize, minContentSize};
    }

    *layout(space, children, styleMap, edges) {
        const inlineSize = resolveInlineSize(space, styleMap);

        const availableInlineSize = inlineSize - edges.all.inline;
        const availableBlockSize =
            resolveBlockSize(constraintSpace, styleMap) - edges.all.block;

        const childFragments = [];
        const childConstraintSpace = new ConstraintSpace({
            inlineSize: availableInlineSize,
            blockSize: availableBlockSize,
        });

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

        let blockOffset = edges.all.blockStart;
        for (let fragment of childFragments) {
            // Position the fragment in a block like manner, centering it in the
            // inline direction.
            fragment.blockOffset = blockOffset;
            fragment.inlineOffset = Math.max(
                edges.all.inlineStart,
                (availableInlineSize - fragment.inlineSize) / 2);

            blockOffset += fragment.blockSize;
        }

        const autoBlockSize = blockOffset + edges.all.blockEnd;
        const blockSize = resolveBlockSize(
            constraintSpace, styleMap, autoBlockSize);

        return {
            inlineSize: inlineSize,
            blockSize: blockSize,
            childFragments: childFragments,
        };
    }
});

A layout API container can communicate with other layout API containers by using the data attribute. This is set by the data member in the FragmentResultOptions dictionary.

The Fragment's breakToken specifies where the LayoutChild last fragmented. If the breakToken is null the LayoutChild wont produce any more Fragments for that token chain. The breakToken can be passed to the layoutNextFragment() function to produce the next Fragment for a particular child. The breakToken cannot be changed. If the current layout requires a different breakToken the author must perform layoutNextFragment() again with different arguments.

Note: In a future level of the specification there may be a way to query for additional baseline information, for example where the alphabetic or center baseline is positioned.

3.3. Constraint Spaces

[Constructor(optional ConstraintSpaceOptions options),Exposed=LayoutWorklet]
interface ConstraintSpace {
    readonly attribute double inlineSize;
    readonly attribute double blockSize;

    readonly attribute boolean inlineSizeFixed;
    readonly attribute boolean blockSizeFixed;

    readonly attribute double percentageInlineSize;
    readonly attribute double percentageBlockSize;

    readonly attribute double? blockFragmentationOffset;
    readonly attribute BlockFragmentationType blockFragmentationType;

    readonly attribute any data;
};

dictionary ConstraintSpaceOptions {
    double inlineSize = Infinity;
    double blockSize = Infinity;

    boolean inlineSizeFixed = false;
    boolean blockSizeFixed = false;

    double? percentageInlineSize = null;
    double? percentageBlockSize = null;

    double? blockFragmentationOffset = null;
    BlockFragmentationType blockFragmentationType = "none";

    any data = null;
};

enum BlockFragmentationType { "none", "page", "column", "region" };
Issue: Should ConstraintSpaceOptions be instead:
const options = {
  availableInlineSize: 100,
  availableBlockSize: Infinity,

  fixedInlineSize: 150, // This makes the engine ignore availableInlineSize.
  fixedBlockSize: 150, // This makes the engine ignore availableBlockSize.

  percentageInlineSize: 100, // This defaults to fixedInlineSize,
                             // then availableInlineSize if not set.
  percentageBlockSize: 100,  // This defaults to fixedBlockSize,
                             // then availableBlockSize if not set.

  blockFragmentationOffset: 200,
  blockFragmentationType: 'column',

  data: {floatPositions: [{x: 20, y: 30}]}, // Author data.
};

The constraint space would mirror this, additionally with the blockSize, and inlineSize attributes which represent the resolved inline and block sizes for the fragment.

A ConstraintSpace is passed into the layout method which represents the available space for the current layout to perform layout inside. It is also used to pass information about the available space into a child layout.

The ConstraintSpace has inlineSize and blockSize attributes. This represents the available space for a Fragment which the layout should respect.

Note: Some layouts may need to produce a Fragment which exceed this size. For example a replaced element. The parent layout should expect this to occur and deal with it appropriately.

A parent layout may require the current layout to be exactly a particular size. If the inlineSizeFixed or blockSizeFixed are true the current layout should produce a Fragment with a fixed size in the appropriate direction.

The layout algorithm performs a flexbox-like distribution of spare space in the inline direction. It creates child constraint spaces which specify that a child should be a fixed inline size.
registerLayout('flex-distribution-like', class {
    *intrinsicSizes(styleMap, children) {
      const childrenSizes = yield children.map((child) => {
          return child.intrinsicSizes();
      });

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

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

      return {maxContentSize, minContentSize};
    }

    *layout(space, children, styleMap, edges, breakToken) {
        const inlineSize = resolveInlineSize(space, styleMap);

        const availableInlineSize = inlineSize - edges.all.inline;
        const availableBlockSize =
            resolveBlockSize(space, styleMap) - edges.all.block;

        const childConstraintSpace = new ConstraintSpace({
            inlineSize: availableInlineSize,
            blockSize: availableBlockSize,
        });

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

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

        // Distribute spare space between children.
        const remainingSpace = Math.max(0, inlineSize - totalSize);
        const extraSpace = remainingSpace / children.length;

        const childFragments = yield children.map((child, i) => {
            return child.layoutNextFragment(new ConstraintSpace({
                inlineSize: unconstrainedSizes[i] + extraSpace,
                inlineSizeFixed: true,
                blockSize: availableBlockSize
            }));
        });

        // Position the fragments.
        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);
        }

        // Resolve our block size.
        const blockSize = resolveBlockSize(space, styleMap, maxChildBlockSize);

        return {
            inlineSize: inlineSize,
            blockSize: blockSize,
            childFragments: childFragments,
        };
    }
});

The ConstraintSpace has percentageInlineSize and percentageBlockSize attributes. These represent the size that a layout percentages should be resolved against while performing layout.

The ConstraintSpace has a blockFragmentationType attribute. The current layout should produce a Fragment which fragments at the blockSize if possible.

The current layout may choose not to fragment a LayoutChild based on the blockFragmentationType, for example if the child has a property like break-inside: avoid-page;.

3.4. Breaking and Fragmentation

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

A LayoutChild can produce multiple Fragments. A LayoutChild may fragment in the block direction if a blockFragmentationType is not none. Additionally LayoutChild which represents inline-level content, may fragment line by line if the child display (set by childDisplay) is "normal".

A subsequent Fragment is produced by using the previous Fragment's breakToken. This tells the child layout to produce a Fragment starting at the point encoded in the ChildBreakToken.

Explain resuming the author defined layout.

This example shows a simple layout which indents child fragments for a certain number of lines.

This example also demonstrates using the previous breakToken of a Fragment to produce the next fragment for the LayoutChild.

It also demonstrates using the BreakToken to respect the ConstraintSpace's blockFragmentationType, it resumes it layout from the previous BreakToken. It returns a FragmentResultOptions with a breakToken which is used to resume the layout.

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

    *layout(space, children, styleMap, edges, breakToken) {
        // Resolve our inline size.
        const inlineSize = resolveInlineSize(space, styleMap);

        // Determine our (inner) available size.
        const availableInlineSize = inlineSize - edges.all.inline;
        const availableBlockSize =
            resolveBlockSize(space, styleMap) - edges.all.block;

        // Detrermine the number of lines to indent, and the indent amount.
        const indent = resolveLength(space, styleMap.get('--indent'));
        let lines = styleMap.get('--indent-lines').value;

        const childFragments = [];

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

            // Remove all the children we have already produced fragments for.
            children.splice(0, children.indexOf(childBreakToken.child));
        }

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

            // Adjust the inline size for the indent.
            const childAvailableInlineSize = shouldIndent ?
                availableInlineSize - indent : availableInlineSize;

            const childSpace = new ConstraintSpace({
                inlineSize: childAvailableInlineSize,
                blockSize: availableBlockSize,
                percentageInlineSize: availableInlineSize,
                blockFragmentationType: space.blockFragmentationType,
            });

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

            // Position the fragment.
            fragment.inlineOffset = shouldIndent ?
                edges.all.inlineStart + indent : edges.all.inlineStart;
            fragment.blockOffset = blockOffset;
            blockOffset += fragment.blockSize;

            // Check if we have gone over the block fragmentation limit.
            if (constraintSpace.blockFragmentationType != 'none' &&
                blockOffset > constraintSpace.blockSize) {
                break;
            }

            if (fragment.breakToken) {
                childBreakToken = fragment.breakToken;
            } else {
                // If a fragment doesn’t have a break token, we move onto the
                // next child.
                child = children.shift();
                childBreakToken = null;
            }
        }

        const autoBlockSize = blockOffset + edges.all.blockEnd;
        const blockSize = resolveBlockSize(space,
                                           styleMap,
                                           autoBlockSize);

        // Return our fragment.
        const result = {
            inlineSize: inlineSize,
            blockSize: blockSize,
            childFragments: childFragments,
        }

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

        return result;
    }
});

3.5. Edges

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

A LayoutEdges object is passed into the layout method. This represents the size of the box model edges for the current box which is being laid out.

The LayoutEdges has border, scrollbar, and padding attributes. Each of these represent the width of their respective edge.

The LayoutEdges has the all attribute. This is a convenience attribute which represents the sum of the border, scrollbar, padding edges.

The LayoutEdgeSizes object represents the width in CSS pixels of an edge in each of the abstract dimensions (inlineStart, inlineEnd, blockStart, blockEnd).

The inline, and block on the LayoutEdgeSizes object are convenience attributes which represent the sum in that direction.

This example shows an node styled by CSS, and what its respective LayoutEdges could contain.
<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(space, children, styleMap, edges, 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).
    }
}

3.6. Utility Functions

[Exposed=LayoutWorklet]
partial interface LayoutWorkletGlobalScope {
    double resolveInlineSize(ConstraintSpace constraintSpace,
                             StylePropertyMapReadOnly styleMap);

    double resolveBlockSize(ConstraintSpace constraintSpace,
                            StylePropertyMapReadOnly styleMap,
                            optional double autoBlockSize);

    double resolveLength(ConstraintSpace constraintSpace, CSSStyleValue value);
};
When the resolveBlockSize(constraintSpace, styleMap, autoBlockSize) method is called, the user agent must run the following steps:

Define that this algorithm should do exactly what CSS does for block sizes. constraintSpace, styleMap, autoBlockSize.

When the resolveLength(constraintSpace, value) method is called, the user agent must run the following steps:

Define that this algorithm should do exactly what CSS does for generic lengths. constraintSpace, value.

4. Interactions with other Modules

This section describes how other CSS modules interact with the CSS Layout API.

4.1. Sizing

User agents must use the ConstraintSpace to communicate to the current layout the size they would like the fragment to be.

If the user agent wishes to force a size on the box, it can use the inlineSizeFixed and blockSizeFixed attributes to do so.

Do we want the inlineSize to always be fixed? This would remove the resolveInlineSize call, and just rely on the pre-defined behaviour by the user agent.

The downside to this is that we won’t be able to things like MathML in the initial version of the specifcation, which is able to "bubble" up inline sizes to its parent.

We can’t do a similar thing for the blockSize due to fragmentation. E.g. the size of the fragment which is undergoing fragmentation, isn’t able to be automatically resolved.

If the layout API container is within a block formatting context, is inflow, and has an auto inline size, the user agent must set the inlineSize to the stretch-fit inline size.

Note: In the example below the layout API container has its inline size set to 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. Positioned layout sizing

If a layout API container is out-of-flow positioned the user agent must solve the positioned size equations (CSS Positioned Layout 3 §8.1 The width of absolute or fixed positioned, non-replaced elements, CSS Positioned Layout 3 §8.3 The height of absolute or fixed positioned, non-replaced elements), and set the appropriate fixed inlineSize and blockSize.

Note: In the example below the layout API container has its inline and block size fixed to 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. Positioning

All positioning in this level of the specification is handled by the user agent.

As a result:

Note: In the example below:

4.3. Overflow

The scrollable overflow for a layout API container is handled by the user agent in this level of the specification.

A layout API container should calculate its scrollable overflow exactly like block containers do.

Even if the author’s layout API container positions a fragment into the scrollable overflow region, relative positioning or transforms may cause the fragment to shift such that its scrollable overflow region, causing no overflow to occur.

4.4. Fragmentation

A parent layout can ask the current layout to fragment by setting the blockFragmentationType and blockFragmentationOffset.

E.g. [css-multicol-1] layout would set a blockFragmentationType to "column" and set the blockFragmentationOffset to where it needs the child to fragment.

4.5. Alignment

Issue: We need to add the ability to request baselines on the constraint space, and have a fragment returned what was asked. E.g.
const spaceOptions = {
    inlineSize: availableInlineSize,
    baselines: ['alphabetic', 'middle'],
};

const fragment = yield child.layoutNextFragment(spaceOptions);
fragment.baselines.get('alphabetic') == /* something */;

Do we want this in level 1? E.g. is it better to wait for implementations of [css-align-3] before doing this?

5. Layout

This section describes how the CSS Layout API interacts with the user agent’s layout engine.

5.1. Layout Invalidation

A document has an associated layout name to input properties map and a layout name to child input properties map. Initially these maps are empty and are populated when registerLayout(name, layoutCtor) is called.

Each box has an associated layout valid flag. It may be either layout-valid or layout-invalid. It is initially set to layout-invalid.

Each box has an associated intrinsic sizes valid flag. If may be either intrinsic-sizes-valid or intrinsic-sizes-invalid. It is initially set to intrinsic-sizes-invalid.

When the computed style for a box changes, the user agent must run the following steps:

  1. Let layoutFunction be the layout() function of the display property on the computed style for the box if it exists. If it is a different type of value (e.g. grid) then abort all these steps.

  2. Let name be the first argument of the layoutFunction.

  3. Let inputProperties be the result of looking up name on layout name to input properties map.

  4. Let childInputProperties be the result of looking up name on layout name to child input properties map.

  5. For each property in inputProperties, if the property’s computed value has changed, set the layout valid flag on the box to layout-invalid, and set the intrinsic sizes valid flag to intrinsic-sizes-invalid.

  6. For each property in childInputProperties, if the property’s computed value has changed, set the layout valid flag on the box to layout-invalid, and set the intrinsic sizes valid flag to intrinsic-sizes-invalid.

When a child box represented by a LayoutChild is added or removed from the box tree or has its layout invalidated (from a computed style change, or a descentdant change). Set the layout valid flag on the current box to layout-invalid and set the intrinsic sizes valid flag on the current box to intrinsic-sizes-invalid.

Note: This only describes layout invalidation as it relates to the CSS Layout API. All boxes conceptually have a layout valid flag and these changes are propagated through the box tree.

We also want to implcitly invalidate on size and edge properties, i.e.

5.2. Concepts

A layout definition is a struct which describes the information needed by the LayoutWorkletGlobalScope about hte author defined layout (which can be referenced by the layout() function). It consists of:

A document layout definition is a struct which describes the information needed by the document about the author defined layout (which can be referenced by the layout() function). It consists of:

5.3. Layout Worklet

The layoutWorklet attribute allows access to the Worklet responsible for all the classes which are related to layout.

The layoutWorklet's worklet global scope type is LayoutWorkletGlobalScope.

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

The LayoutWorkletGlobalScope is the global execution context of the layoutWorklet.

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

5.4. Registering A Layout

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

"normal" is a bad name?

The document has a map of document layout definitions. Initially this map is empty; it is populated when registerLayout(name, layoutCtor) is called.

The LayoutWorkletGlobalScope has a map of layout definitions. Initially this map is empty; it is populated when registerLayout(name, layoutCtor) is called.

The LayoutWorkletGlobalScope has a map of layout class instances. Initially this map is empty; it is populated when the user agent calls either determine the intrinsic sizes or generate a fragment for a box.

When the registerLayout(name, layoutCtor) method is called, the user agent must run the following steps:
  1. If the name is an empty string, throw a TypeError and abort all these steps.

  2. Let layoutDefinitionMap be LayoutWorkletGlobalScope's layout definitions map.

  3. If layoutDefinitionMap[name] exists throw a "InvalidModificationError" DOMException and abort all these steps.

  4. Let inputProperties be an empty sequence<DOMString>.

  5. Let inputPropertiesIterable be the result of Get(layoutCtor, "inputProperties").

  6. If inputPropertiesIterable is not undefined, then set inputProperties to the result of converting inputPropertiesIterable to a sequence<DOMString>. If an exception is thrown, rethrow the exception and abort all these steps.

    Note: The list of CSS properties provided by the input properties getter can either be custom or native CSS properties.

    Note: The list of CSS properties may contain shorthands.

    Note: In order for a layout class to be forwards compatible, the list of CSS properties can also contains currently invalid properties for the user agent. For example margin-bikeshed-property.

  7. Let childInputProperties be an empty sequence<DOMString>.

  8. Let childInputPropertiesIterable be the result of Get(layoutCtor, "childInputProperties").

  9. If childInputPropertiesIterable is not undefined, then set childInputProperties to the result of converting childInputPropertiesIterable to a sequence<DOMString>. If an exception is thrown, rethrow the exception and abort all these steps.

  10. Let childDisplay be a ChildDisplayType set to "block".

  11. Let childDisplayValue be the result of Get(layoutCtor, "childDisplay").

  12. If childDisplayValue if not undefined, then then childDisplay to the result of converting childDisplayValue to a ChildDisplayType. If an exception is thrown, rethrow the exception and abort all these steps.

  13. If the result of IsConstructor(layoutCtor) is false, throw a TypeError and abort all these steps.

  14. Let prototype be the result of Get(layoutCtor, "prototype").

  15. If the result of Type(prototype) is not Object, throw a TypeError and abort all these steps.

  16. Let layout be the result of Get(prototype, "layout").

  17. If the result of IsCallable(layout) is false, throw a TypeError and abort all these steps.

  18. If layout’s [[FunctionKind]] internal slot is not "generator", throw a TypeError and abort all these steps.

  19. Let intrinsicSizes be the result of Get(prototype, "intrinsicSizes").

  20. If the result of IsCallable(intrinsicSizes) is false, throw a TypeError and abort all these steps.

  21. If intrinsicSizes’s [[FunctionKind]] internal slot is not "generator", throw a TypeError and abort all these steps.

  22. Let definition be a new layout definition with:

  23. Set layoutDefinitionMap[name] to definition.

  24. Queue a task to run the following steps:

    1. Let documentLayoutDefinitionMap be the associated document’s document layout definitions map.

    2. Let documentDefinition be a new document layout definition with:

    3. If documentLayoutDefinitionMap[name] exists, run the following steps:

      1. Let existingDocumentDefinition be the result of get documentLayoutDefinitionMap[name].

      2. If existingDocumentDefinition is "invalid", abort all these steps.

      3. If existingDocumentDefinition and documentDefinition are not equivalent, (that is input properties, child input properties, and child display are different), then:

        Set documentLayoutDefinitionMap[name] to "invalid".

        Log an error to the debugging console stating that the same class was registered with different inputProperties, childInputProperties, or childDisplay.

    4. Otherwise, set documentLayoutDefinitionMap[name] to documentDefinition.

Note: The shape of the class should be:
class MyLayout {
    static get inputProperties() { return ['--foo']; }
    static get childrenInputProperties() { return ['--bar']; }
    static get childDisplay() { return 'normal'; }

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

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

5.5. Layout Engine

[Exposed=LayoutWorklet]
interface FragmentRequest {
  // Has internal slots:
  // [[layoutChild]] - The layout child to generate the fragment for.
  // [[constraintSpace]] - The constraint space to perform layout in.
  // [[breakToken]] - The break token to resume the layout with.
};

[Exposed=LayoutWorklet]
interface IntrinsicSizesRequest {
  // Has internal slots:
  // [[layoutChild]] - The layout child to calculate the intrinsic sizes for.
};

The layout method and intrinsic sizes method on the author supplied layout class is a generator function instead of a regular javascript function. This is for user-agents to be able to support asynchronous and parallel layout engines.

When an author invokes the layoutNextFragment() method on a LayoutChild the user-agent doesn’t synchronously generate a Fragment to return to the author’s code. Instead it returns a FragmentRequest. This is a completely opaque object to the author but contains internal slots which encapsulates the layoutNextFragment() method call.

When a FragmentRequest(s) are yielded from a layout generator object the user-agent’s layout engine may run the algorithm asynchronously with other work, and/or on a different thread of execution. When Fragment(s) have been produced by the engine, the user-agent will "tick" the generator object with the resulting Fragment(s).

The same applies for the intrinsicSizes() method.

An example layout engine written in javascript is shown below.
class LayoutEngine {
  // This function takes the root of the box-tree, a ConstraintSpace, and a
  // BreakToken to (if paginating for printing for example) and generates a
  // Fragment.
  layoutEntry(rootBox, rootPageConstraintSpace, breakToken) {
    return layoutFragment({
      box: rootBox,
      constraintSpace: rootPageConstraintSpace,
      breakToken: breakToken,
    });
  }

  // This function takes a FragmentRequest and calls the appropriate layout
  // algorithm to generate the a Fragment.
  layoutFragment(fragmentRequest) {
    const box = fragmentRequest.layoutChild;
    const algorithm = selectLayoutAlgorithmForBox(box);
    const fragmentRequestGenerator = algorithm.layout(
        fragmentRequest.constraintSpace,
        box.children,
        box.styleMap,
        fragmentRequest.breakToken);

    let nextFragmentRequest = fragmentRequestGenerator.next();

    while (!nextFragmentRequest.done) {
      // A user-agent may decide to perform layout to generate the fragments in
      // parallel on separate threads. This example performs them synchronously
      // in order.
      let fragments = nextFragmentRequest.value.map(layoutFragment);

      // A user-agent may decide to yield for other work (garbage collection for
      // example) before resuming this layout work. This example just performs
      // layout synchronously without any ability to yield.
      nextFragmentRequest = fragmentRequestGenerator.next(fragments);
    }

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

5.6. Performing Layout

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

dictionary IntrinsicSizesResultOptions {
    double maxContentSize;
    double minContentSize;
};

interface IntrinsicSizes {
  readonly attribute double minContentSize;
  readonly attribute double maxContentSize;
};

Should minContentSize and maxContentSize be content contributions instead? E.g. minContentContribution, maxContentContribution. Should minContentSize and maxContentSize be content contributions as well?

5.6.1. Determining Intrinsic Sizes

The determine the intrinsic sizes algorithm defines how a user agent is to query the author defined layout for a box’s intrinsic sizes information.

Note: The determine the intrinsic sizes algorithm allows for user agents to cache an arbitary number of previous invocations to reuse.

When the user agent wants to determine the intrinsic sizes of a layout API formatting context for a given box, childBoxes it must run the following steps:
  1. Let layoutFunction be the layout() for the computed value of <display-inside> for box.

  2. If the intrinsic sizes valid flag for the layoutFunction is intrinsic-sizes-valid the user agent may use the intrinsic sizes from the previous invocation. If so it may abort all these steps and use the previous value for the intrinsic sizes.

  3. Set the intrinsic sizes valid flag for the layoutFunction to intrinsic-sizes-valid.

  4. Let name be the first argument of the layoutFunction.

  5. Let documentDefinition be the result of get a document layout definition.

    If get a document layout definition returned failure, or if documentDefinition is "invalid", then let box fallback to the flow layout and abort all these steps.

  6. Let workletGlobalScope be a LayoutWorkletGlobalScope from the list of worklet’s WorkletGlobalScopes from the layout Worklet.

    The user agent must have, and select from at least two LayoutWorkletGlobalScopes in the worklet’s WorkletGlobalScopes list, unless the user agent is under memory constraints.

    Note: This is to ensure that authers do not rely on being able to store state on the global object or non-regeneratable state on the class.

    The user agent may also create a WorkletGlobalScope at this time, given the layout Worklet.

  7. Run invoke a intrinsic sizes callback given name, box, childBoxes, and workletGlobalScope optionally in parallel.

    Note: If the user agent runs invoke a intrinsic sizes callback on a thread in parallel, it should select a layout worklet global scope which can be used on that thread.

When the user agent wants to invoke a intrinsic sizes callback given name, box, childBoxes, and workletGlobalScope, it must run the following steps:
  1. Let definition be the result of get a layout definition given name, and workletGlobalScope.

    If get a layout definition returned failure, let the box fallback to the flow layout and abort all these steps.

  2. Let layoutInstance be the result of get a layout class instance given name, box, definition, workletGlobalScope.

    If get a layout class instance returned failure, let the box fallback to the flow layout and abort all these steps.

  3. Let inputProperties be definition’s input properties.

  4. Let childInputProperties be definition’s child input properties.

  5. Let styleMap be a new StylePropertyMapReadOnly populated with only the computed value’s for properties listed in inputProperties for box.

  6. Let children be a new list populated with new LayoutChild objects which represent childBoxes.

    The styleMap on each LayoutChild should be a new StylePropertyMapReadOnly populated with only the computed value’s for properties listed in childInputProperties.

  7. At this stage the user agent may re-use the intrinsic sizes from a previous invocation if children, styleMap are equivalent to that previous invocation. If so let the intrinsic sizes the cached intrinsic sizes and abort all these steps.

  8. Let intrinsicSizesGeneratorFunction be definition’s intrinsic sizes generator function.

  9. Let intrinsicSizesGenerator be the result of Invoke(intrinsicSizesGeneratorFunction, layoutInstance, «styleMap, children»).

    If an exception is thrown the let box fallback to the flow layout and abort all these steps.

  10. Let intrinsicSizesValue be the result of run a generator given intrinsicSizesGenerator, and "intrinsic-sizes".

    If run a generator returned failure, then let box fallback to the flow layout and abort all these steps.

  11. Let intrinsicSizes be the result of converting intrinsicSizesValue to a IntrinsicSizesResultOptions. If an exception is thrown, let box fallback to the flow layout and abort all these steps.

  12. Set the intrinsic sizes of box:

5.6.2. Generating Fragments

The generate a fragment algorithm defines how a user agent is to generate a box’s fragment for an author defined layout.

Note: The generate a fragment algorithm allows for user agents to cache an arbitary number of previous invocations to reuse.

When the user agent wants to generate a fragment of a layout API formatting context for a given box, childBoxes, internalConstraintSpace, and an optional internalBreakToken it must run the following steps:
  1. Let layoutFunction be the layout() for the computed value of <display-inside> for box.

  2. If the layout valid flag for the layoutFunction is layout-valid the user agent may use the intrinsic sizes from the previous invocation. If so it may abort all these steps and use the previous value for the intrinsic sizes.

  3. Set the layout valid flag for the layoutFunction to layout-valid.

  4. Let name be the first argument of the layoutFunction.

  5. Let documentDefinition be the result of get a document layout definition.

    If get a document layout definition returned failure, or if documentDefinition is "invalid", then let box fallback to the flow layout and abort all these steps.

  6. Let workletGlobalScope be a LayoutWorkletGlobalScope from the list of worklet’s WorkletGlobalScopes from the layout Worklet.

    The user agent must have, and select from at least two LayoutWorkletGlobalScopes in the worklet’s WorkletGlobalScopes list, unless the user agent is under memory constraints.

    Note: This is to ensure that authers do not rely on being able to store state on the global object or non-regeneratable state on the class.

    The user agent may also create a WorkletGlobalScope at this time, given the layout Worklet.

  7. Run invoke a layout callback given name, box, childBoxes, internalConstraintSpace, internalBreakToken, and workletGlobalScope optionally in parallel.

    Note: If the user agent runs invoke a intrinsic sizes callback on a thread in parallel, it should select a layout worklet global scope which can be used on that thread.

When the user agent wants to invoke a layout callback given name, box, childBoxes, internalConstraintSpace, internalBreakToken, and workletGlobalScope, it must run the following steps:
  1. Let definition be the result of get a layout definition given name, and workletGlobalScope.

    If get a layout definition returned failure, let the box fallback to the flow layout and abort all these steps.

  2. Let layoutInstance be the result of get a layout class instance given name, box, definition, workletGlobalScope.

    If get a layout class instance returned failure, let the box fallback to the flow layout and abort all these steps.

  3. Let inputProperties be definition’s input properties.

  4. Let childInputProperties be definition’s child input properties.

  5. Let styleMap be a new StylePropertyMapReadOnly populated with only the computed value’s for properties listed in inputProperties for box.

  6. Let children be a new list populated with new LayoutChild objects which represent childBoxes.

    The styleMap on each LayoutChild should be a new StylePropertyMapReadOnly populated with only the computed value’s for properties listed in childInputProperties.

  7. Let constraintSpace be a new ConstraintSpace populated with the appropriate information from internalConstraintSpace.

  8. Let edges be a new LayoutEdgeSizes populated with the computed value for all the box model edges for box.

  9. Let breakToken be a new BreakToken populated with the appropriate information from internalBreakToken.

    If internalBreakToken is null, let breakToken be null.

  10. At this stage the user agent may re-use a fragment from a previous invocation if children, styleMap, constraintSpace, breakToken are equivalent to that previous invocation. If so let the fragment output be that cached fragment and abort all these steps.

  11. Let layoutGeneratorFunction be definition’s layout generator function.

  12. Let layoutGenerator be the result of Invoke(layoutGeneratorFunction, layoutInstance, «styleMap, children, constraintSpace, edges, breakToken»).

    If an exception is thrown the let box fallback to the flow layout and abort all these steps.

  13. Let fragmentValue be the result of run a generator given layoutGenerator, and "layout".

    If run a generator returned failure, then let box fallback to the flow layout and abort all these steps.

  14. Let fragment be the result of converting fragmentValue to a FragmentResultOptions. If an exception is thrown, let box fallback to the flow layout and abort all these steps.

  15. Create a fragment for box with:

5.6.3. Utility Algorithms

The section specifies algorithms common to the determine the intrinsic sizes and generate a fragment algorithms.

When the user agent wants to get a document layout definition given name, it must run the following steps:
  1. Let documentLayoutDefinitionMap be the associated document’s document layout definitions map.

  2. If documentLayoutDefinitionMap[name] does not exist, return failure and abort all these steps.

  3. Return the result of get documentLayoutDefinitionMap[name].

When the user agent wants to get a layout definition given name, and workletGlobalScope, it must run the following steps:
  1. Let layoutDefinitionMap be workletGlobalScope’s layout definitions map.

  2. If layoutDefinitionMap[name] does not exist, run the following steps:

    1. Queue a task to run the following steps:

      1. Let documentLayoutDefinitionMap be the associated document’s document layout definition map.

      2. Set documentLayoutDefinitionMap[name] to "invalid".

      3. The user agent should log an error to the debugging console stating that a class wasn’t registered in all LayoutWorkletGlobalScopes.

    2. Return failure, and abort all these steps.

  3. Return the result of get layoutDefinitionMap[name].

When the user agent wants to get a layout class instance given name, box, definition, and workletGlobalScope, it must run the following steps:
  1. Let layoutClassInstanceMap be workletGlobalScope’s layout class instances map.

  2. Let key be a stable key which is unique to box and name.

  3. Let layoutInstance be the result of get layoutClassInstanceMap[key]. If layoutInstance is null, run the following steps:

    1. If the constructor valid flag on definition is false, let box fallback to the flow layout and a abort all these steps.

    2. Let layoutCtor be the class constructor on definition.

    3. Let layoutInstance be the result of Construct(layoutCtor).

      If construct throws an exception, set the definition’s constructor valid flag to false, then return failure and abort all these steps.

    4. Set layoutClassInstanceMap[key] to layoutInstance.

  4. Return layoutInstance.

When the user agent wants to run a generator given generator, and generatorType, it must run the following steps:
  1. Let nextResult be the result of calling Invoke(next, generator).

    If an exception is thrown return failure, and abort all these steps.

  2. Perform the following substeps until the result of Get(nextResult, "done") is true.

    1. Let requests be the result of Get(nextResult, "value").

    2. Let results be an empty list.

    3. For each request in requests perform the following substeps:

      1. Let result be null.

      2. If request is a IntrinsicSizesRequest then:

        1. Let layoutChild be the result of looking up the internal slot [[layoutChild]] on request.

        2. Let result be a new IntrinsicSizes with:

      3. If request is a FragmentRequest and generatorType is "layout" then:

        1. Let layoutChild be result of looking up the internal slot [[layoutChild]] on request.

        2. Let childConstraintSpace be the result of looking up the internal slot [[childConstraintSpace]] on request.

        3. Let childBreakToken be the result of looking up the internal slot [[childBreakToken]] on request.

        4. Let internalFragment be the result of the user agent producing a fragment based on layoutChild, childConstraintSpace, and childBreakToken.

        5. Let targetRealm be generator’s Realm.

        6. Let result be a new Fragment with:

      4. If result is null (that is neither of the above branches was taken), return failure, and abort all these steps.

      5. Append result to results.

    The user agent may perform the above loop out of order, and in parallel. The ordering for requests and results however must be consistent.

    Note: This is to allow user agents to run the appropriate layout algorithm on a different thread, or asynchronously (e.g. time slicing layout work with other work). If the user agent performs the loop in parallel, the outside loop has to wait until all the cross thread tasks are complete before calling the generator again. It cannot return partial results to the author.

    1. Let nextResult be the result of calling Invoke(next, generator, results).

      If an exception is thrown then return failure, and abort all these steps.

  3. Return the result of calling Get(nextResult, "value").

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Advisements are normative sections styled to evoke special attention and are set apart from other normative text with <strong class="advisement">, like this: UAs MUST provide an accessible alternative.

Conformance classes

Conformance to this specification is defined for three conformance classes:

style sheet
A CSS style sheet.
renderer
A UA that interprets the semantics of a style sheet and renders documents that use them.
authoring tool
A UA that writes a style sheet.

A style sheet is conformant to this specification if all of its statements that use syntax defined in this module are valid according to the generic CSS grammar and the individual grammars of each feature defined in this module.

A renderer is conformant to this specification if, in addition to interpreting the style sheet as defined by the appropriate specifications, it supports all the features defined by this specification by parsing them correctly and rendering the document accordingly. However, the inability of a UA to correctly render a document due to limitations of the device does not make the UA non-conformant. (For example, a UA is not required to render color on a monochrome monitor.)

An authoring tool is conformant to this specification if it writes style sheets that are syntactically correct according to the generic CSS grammar and the individual grammars of each feature in this module, and meet all other conformance requirements of style sheets as described in this module.

Partial implementations

So that authors can exploit the forward-compatible parsing rules to assign fallback values, CSS renderers must treat as invalid (and ignore as appropriate) any at-rules, properties, property values, keywords, and other syntactic constructs for which they have no usable level of support. In particular, user agents must not selectively ignore unsupported component values and honor supported values in a single multi-value property declaration: if any value is considered invalid (as unsupported values must be), CSS requires that the entire declaration be ignored.

Implementations of Unstable and Proprietary Features

To avoid clashes with future stable CSS features, the CSSWG recommends following best practices for the implementation of unstable features and proprietary extensions to CSS.

Non-experimental implementations

Once a specification reaches the Candidate Recommendation stage, non-experimental implementations are possible, and implementors should release an unprefixed implementation of any CR-level feature they can demonstrate to be correctly implemented according to spec.

To establish and maintain the interoperability of CSS across implementations, the CSS Working Group requests that non-experimental CSS renderers submit an implementation report (and, if necessary, the testcases used for that implementation report) to the W3C before releasing an unprefixed implementation of any CSS features. Testcases submitted to W3C are subject to review and correction by the CSS Working Group.

Further information on submitting testcases and implementation reports can be found from on the CSS Working Group’s website at http://www.w3.org/Style/CSS/Test/. Questions should be directed to the public-css-testsuite@w3.org mailing list.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-BACKGROUNDS-3]
Bert Bos; Elika Etemad; Brad Kemper. CSS Backgrounds and Borders Module Level 3. 17 October 2017. CR. URL: https://www.w3.org/TR/css-backgrounds-3/
[CSS-BREAK-3]
Rossen Atanassov; Elika Etemad. CSS Fragmentation Module Level 3. 9 February 2017. CR. URL: https://www.w3.org/TR/css-break-3/
[CSS-CASCADE-4]
Elika Etemad; Tab Atkins Jr.. CSS Cascading and Inheritance Level 4. 14 January 2016. 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. 19 October 2017. 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. 9 May 2017. 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. 24 May 2016. WD. URL: https://www.w3.org/TR/css-inline-3/
[CSS-OVERFLOW-3]
David Baron; Florian Rivoal. CSS Overflow Module Level 3. 31 May 2016. WD. URL: https://www.w3.org/TR/css-overflow-3/
[CSS-PAGE-FLOATS-3]
Johannes Wilm. CSS Page Floats. 15 September 2015. WD. URL: https://www.w3.org/TR/css-page-floats-3/
[CSS-POSITION-3]
Rossen Atanassov; Arron Eicholz. CSS Positioned Layout Module Level 3. 17 May 2016. 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. 7 June 2016. WD. URL: https://www.w3.org/TR/css-pseudo-4/
[CSS-SIZING-3]
Elika Etemad. CSS Intrinsic & Extrinsic Sizing Module Level 3. 7 February 2017. WD. URL: https://www.w3.org/TR/css-sizing-3/
[CSS-TYPED-OM-1]
Shane Stephens; Tab Atkins Jr.. CSS Typed OM Level 1. 1 August 2017. 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. 29 September 2016. CR. URL: https://www.w3.org/TR/css-values-3/
[CSS-WRITING-MODES-4]
CSS Writing Modes Module Level 4 URL: https://drafts.csswg.org/css-writing-modes-4/
[CSS21]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. 7 June 2011. REC. URL: https://www.w3.org/TR/CSS2/
[CSS22]
Bert Bos. Cascading Style Sheets Level 2 Revision 2 (CSS 2.2) Specification. 12 April 2016. WD. URL: https://www.w3.org/TR/CSS22/
[CSS3-DISPLAY]
Elika Etemad. CSS Display Module Level 3. 20 July 2017. WD. URL: https://www.w3.org/TR/css-display-3/
[CSSOM-1]
Simon Pieters; Glenn Adams. CSS Object Model (CSSOM). 17 March 2016. WD. URL: https://www.w3.org/TR/cssom-1/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[FONT-METRICS-API-1]
Font Metrics API Module Level 1 URL: https://drafts.css-houdini.org/font-metrics-api-1/
[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. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[WebIDL]
Cameron McCormack; Boris Zbarsky; Tobie Langel. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/
[WORKLETS-1]
Ian Kilpatrick. Worklets Level 1. 7 June 2016. WD. URL: https://www.w3.org/TR/worklets-1/

Informative References

[CSS-ALIGN-3]
Elika Etemad; Tab Atkins Jr.. CSS Box Alignment Module Level 3. 6 September 2017. WD. URL: https://www.w3.org/TR/css-align-3/
[CSS-MULTICOL-1]
Håkon Wium Lie; Florian Rivoal; Rachel Andrew. CSS Multi-column Layout Module Level 1. 5 October 2017. WD. URL: https://www.w3.org/TR/css-multicol-1/

Property Index

No properties defined.

IDL Index

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

    IntrinsicSizesRequest intrinsicSizes();
    FragmentRequest layoutNextFragment(ConstraintSpace space, ChildBreakToken breakToken);
};

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

    attribute double inlineOffset;
    attribute double blockOffset;

    readonly attribute any data;

    readonly attribute ChildBreakToken? breakToken;
};

/* NOTE EVERYTHING BELOW HERE IS SPECULATIVE */
interface LineFragment : Fragment {
    readonly attribute sequence<LineFragmentSegment> segments;
};

interface LineFragmentSegment {
    readonly attribute LineFragmentSegmentType type;
    readonly attribute LineFragmentSegmentBreakType breakType;
    readonly attribute FontMetrics? metrics;
    readonly attribute double inlineOffset;
};

enum LineFragmentSegmentType {
    "word-break",
    "atomic-inline",
    /* TODO add others here, not sure yet. */
};

enum LineFragmentSegmentBreakType {
    "something"
    /* TODO add others here, not sure yet. */
};

[Constructor(optional ConstraintSpaceOptions options),Exposed=LayoutWorklet]
interface ConstraintSpace {
    readonly attribute double inlineSize;
    readonly attribute double blockSize;

    readonly attribute boolean inlineSizeFixed;
    readonly attribute boolean blockSizeFixed;

    readonly attribute double percentageInlineSize;
    readonly attribute double percentageBlockSize;

    readonly attribute double? blockFragmentationOffset;
    readonly attribute BlockFragmentationType blockFragmentationType;

    readonly attribute any data;
};

dictionary ConstraintSpaceOptions {
    double inlineSize = Infinity;
    double blockSize = Infinity;

    boolean inlineSizeFixed = false;
    boolean blockSizeFixed = false;

    double? percentageInlineSize = null;
    double? percentageBlockSize = null;

    double? blockFragmentationOffset = null;
    BlockFragmentationType blockFragmentationType = "none";

    any data = null;
};

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;
};

[Exposed=LayoutWorklet]
partial interface LayoutWorkletGlobalScope {
    double resolveInlineSize(ConstraintSpace constraintSpace,
                             StylePropertyMapReadOnly styleMap);

    double resolveBlockSize(ConstraintSpace constraintSpace,
                            StylePropertyMapReadOnly styleMap,
                            optional double autoBlockSize);

    double resolveLength(ConstraintSpace constraintSpace, CSSStyleValue value);
};

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

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

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

[Exposed=LayoutWorklet]
interface FragmentRequest {
  // Has internal slots:
  // [[layoutChild]] - The layout child to generate the fragment for.
  // [[constraintSpace]] - The constraint space to perform layout in.
  // [[breakToken]] - The break token to resume the layout with.
};

[Exposed=LayoutWorklet]
interface IntrinsicSizesRequest {
  // Has internal slots:
  // [[layoutChild]] - The layout child to calculate the intrinsic sizes for.
};

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

dictionary IntrinsicSizesResultOptions {
    double maxContentSize;
    double minContentSize;
};

interface IntrinsicSizes {
  readonly attribute double minContentSize;
  readonly attribute double maxContentSize;
};

Issues Index

Issue: Should ConstraintSpaceOptions be instead:
const options = {
  availableInlineSize: 100,
  availableBlockSize: Infinity,

  fixedInlineSize: 150, // This makes the engine ignore availableInlineSize.
  fixedBlockSize: 150, // This makes the engine ignore availableBlockSize.

  percentageInlineSize: 100, // This defaults to fixedInlineSize,
                             // then availableInlineSize if not set.
  percentageBlockSize: 100,  // This defaults to fixedBlockSize,
                             // then availableBlockSize if not set.

  blockFragmentationOffset: 200,
  blockFragmentationType: 'column',

  data: {floatPositions: [{x: 20, y: 30}]}, // Author data.
};

The constraint space would mirror this, additionally with the blockSize, and inlineSize attributes which represent the resolved inline and block sizes for the fragment.

Explain resuming the author defined layout.
Define that this algorithm should do exactly what CSS does for block sizes. constraintSpace, styleMap, autoBlockSize.
Define that this algorithm should do exactly what CSS does for generic lengths. constraintSpace, value.
Do we want the inlineSize to always be fixed? This would remove the resolveInlineSize call, and just rely on the pre-defined behaviour by the user agent.
Issue: We need to add the ability to request baselines on the constraint space, and have a fragment returned what was asked. E.g.
const spaceOptions = {
    inlineSize: availableInlineSize,
    baselines: ['alphabetic', 'middle'],
};

const fragment = yield child.layoutNextFragment(spaceOptions);
fragment.baselines.get('alphabetic') == /* something */;

Do we want this in level 1? E.g. is it better to wait for implementations of [css-align-3] before doing this?

We also want to implcitly invalidate on size and edge properties, i.e.
"normal" is a bad name?
Should minContentSize and maxContentSize be content contributions instead? E.g. minContentContribution, maxContentContribution. Should minContentSize and maxContentSize be content contributions as well?