Title:  CSS Layout API Level 1
Status: DREAM
Group: houdini
ED: https://drafts.css-houdini.org/css-layout-api-1/
Shortname: css-layout-api
Level: 1
Abstract:
Editor: Greg Whitworth, gwhit@microsoft.com 
Editor: Ian Kilpatrick, ikilpatrick@chromium.org
Editor: Tab Atkins, jackalmage@gmail.com
Editor: Shane Stephens, shanestephens@google.com
Editor: Robert O'Callahan, robert@ocallahan.org
Editor: Rossen Atanassov, rossen.atanassov@microsoft.com
Ignored Terms: LayoutWorklet
Ignored Terms: create a workletglobalscope
urlPrefix: https://heycam.github.io/webidl/; type: dfn;
    text: InvalidModificationError
    urlPrefix: #dfn-;
        url: throw; text: thrown
    urlPrefix: #idl-;
        text: boolean
        text: DOMException
    url: es-type-mapping; text: converting
    url: es-invoking-callback-functions; text: Invoke
urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
    text: constructor
    text: Construct
    text: IsArray
    text: IsCallable
    text: IsConstructor
    text: HasProperty
    url: ecmascript-data-types-and-values; text: type
    url: get-o-p; text: Get
    url: generatorfunction; text: generator function
    url: terms-and-definitions-function; text: function
    urlPrefix: native-error-types-used-in-this-standard-
        text: TypeError
urlPrefix: https://drafts.csswg.org/css-display-3/#; type: dfn
    text: flow layout
urlPrefix: https://drafts.csswg.org/css-sizing/#; type: dfn
    text: intrinsic sizes
urlPrefix: https://drafts.csswg.org/css-break/#; type: dfn
    text: fragmentation break
urlPrefix: https://www.w3.org/TR/CSS21/box.html#; type: dfn
    url: box-dimensions; text: box model edges
Introduction {#intro} ===================== 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. Layout API Containers {#layout-api-containers} ==============================================
    Name: <>
    New values: layout(<>)
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 <> 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. 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 [[#interaction-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. Layout API Container Painting {#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 {{FragmentResultOptions/childFragments}}) is used in place of raw document order, and 'z-index' values other than ''z-index/auto'' create a stacking context even if 'position' is ''static''. Layout API Model and Terminology {#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. Layout Children {#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 {{Fragment}}s 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 {{LayoutChild/styleMap}}. The {{LayoutChild/styleMap}} will only contain properties which are listed in the child input properties array. A {{LayoutChild}} could be generated by: - An element. - A root inline box. - A ::before or ::after pseudo-element. Note: Other pseudo-elements such as ::first-letter or ::first-line do not generate a {{LayoutChild}} for layout purposes. They are additional styling information for a text node. - An anonymous box. For example an anonymous box may be inserted as a result of: - A text node which has undergone blockification. (Or more generally a root inline box which has undergone blockification). - An element with ''display: table-cell'' which doesn't have a parent with ''display: table''.
Note: As an example the following would be placed into three {{LayoutChild}}ren:
        <style>
          #box::before { content: 'hello!'; }
        </style>
        
        <div id="box">A block level box with text.</div>
        <img src="..." />
    
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 {{LayoutChild}}ren 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 {{LayoutChild/layoutNextFragment()}} method. This will produce a {{Fragment}} which contains layout information. The {{LayoutChild/layoutNextFragment()}} method may be invoked multiple times with different arguments to query the {{LayoutChild}} for different layout information. Layout Fragments {#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;
};
A {{Fragment}} represents a CSS fragment of a {{LayoutChild}} after layout has occurred on that child. This is produced by the {{LayoutChild/layoutNextFragment()}} method. The {{Fragment}} has {{Fragment/inlineSize}} and {{Fragment/blockSize}} attributes, which are set by the respective child's layout algorithm. They cannot be changed. If the current layout requires a different {{Fragment/inlineSize}} or {{Fragment/blockSize}} the author must perform {{LayoutChild/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 {{Fragment/inlineOffset}} and {{Fragment/blockOffset}} attributes. If not set by the author they default to zero.
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,
        });

        let blockOffset = edges.all.blockStart;

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

        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 contentSize = blockOffset + edges.all.blockEnd;
        const blockSize = resolveBlockSize(
            constraintSpace, styleMap, contentSize);

        return {
            inlineSize: inlineSize,
            blockSize: blockSize,
            childFragments: childFragments,
        };
    }
});
The {{Fragment}}'s {{Fragment/breakToken}} specifies where the {{LayoutChild}} last fragmented. If the {{Fragment/breakToken}} is null the {{LayoutChild}} wont produce any more {{Fragment}}s for that token chain. The {{Fragment/breakToken}} can be passed to the {{LayoutChild/layoutNextFragment()}} function to produce the next {{Fragment}} for a particular child. The {{Fragment/breakToken}} cannot be changed. If the current layout requires a different {{Fragment/breakToken}} the author must perform {{LayoutChild/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. Constraint Spaces {#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 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;

    BlockFragmentationType blockFragmentationType = "none";

    any data = null;
};

enum BlockFragmentationType { "none", "page", "column", "region" };
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 {{ConstraintSpace/inlineSize}} and {{ConstraintSpace/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 {{ConstraintSpace/inlineSizeFixed}} or {{ConstraintSpace/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 {{ConstraintSpace/percentageInlineSize}} and {{ConstraintSpace/percentageBlockSize}} attributes. These represent the size that a layout percentages should be resolved against while performing layout. The {{ConstraintSpace}} has a {{ConstraintSpace/blockFragmentationType}} attribute. The current layout should produce a {{Fragment}} which fragments at the {{ConstraintSpace/blockSize}} if possible. The current layout may choose not to fragment a {{LayoutChild}} based on the {{ConstraintSpace/blockFragmentationType}}, for example if the child has a property like ''break-inside: avoid-page;''. Breaking and Fragmentation {#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", "inline", "inline-hyphen", "column", "page", "region" };
Issue: Fill out other inline type break types. A {{LayoutChild}} can produce multiple {{Fragment}}s. A {{LayoutChild}} may fragment in the block direction if a {{ConstraintSpace/blockFragmentationType}} is not none. Additionally {{LayoutChild}} which represents inline-level content, may fragment line by line if the displayType is "normal". A subsequent {{Fragment}} is produced by using the previous {{Fragment}}'s {{Fragment/breakToken}}. This tells the child layout to produce a {{Fragment}} starting at the point encoded in the {{ChildBreakToken}}. Issue: Explain resuming the author defined layout.
This example shows a simple inline layout which places child fragments in the inline direction. This example also demonstrates using the previous {{Fragment/breakToken}} of a {{Fragment}} to produce the next fragment for the {{LayoutChild}}. It also demonstrates using the {{BreakToken}} to respect the {{ConstraintSpace}}'s {{ConstraintSpace/blockFragmentationType}}, it resumes it layout from the previous {{BreakToken}}. It returns a {{FragmentResultOptions}} with a {{FragmentResultOptions/breakToken}} which is used to resume the layout.
registerLayout('basic-inline', class {
    static displayType = 'normal';

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

        const childFragments = [];

        let currentLine = [];
        let usedInlineSize = 0;

        let lineOffset = 0;
        let maxLineBlockSize = 0;

        // Just a small little function which will update the above variables.
        const nextLine = function() {
            currentLine = [];
            usedInlineSize = 0;

            lineOffset += maxLineBlockSize;
            maxLineBlockSize = 0;
        }

        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 child = children.shift();
        while (child) {
            // Make sure we actually have space on the current line.
            if (usedInlineSize > availableInlineSize) {
                nextLine();
            }

            // The constraint space here will have the inline size of the
            // remaining space on the line.
            const remainingInlineSize = availableInlineSize - usedInlineSize;
            const constraintSpace = new ConstraintSpace({
                inlineSize: availableInlineSize - usedInlineSize,
                blockSize: availableBlockSize,
                percentageInlineSize: availableInlineSize,
            });

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

            // Check if there is still space on the current line.
            if (fragment.inlineSize > remainingInlineSize) {
                nextLine();

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

            // Insert fragment on the current line.
            currentLine.push(fragment);
            fragment.inlineOffset = usedInlineSize;

            // Go through each of the fragments on the line and update their
            // block offsets.
            for (let fragmentOnLine of currentLine) {
                fragmentOnLine.blockOffset = lineOffset;

                const lineBlockSize =
                    fragmentOnLine.blockOffset + fragmentOnLine.blockSize;
                if (maxLineBlockSize < lineBlockSize) {
                    maxLineBlockSize = lineBlockSize;
                }
            }

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

        // Determine our block size.
        nextLine();
        const contentSize = lineOffset + edges.all.block;
        const blockSize = resolveBlockSize(space,
                                           styleMap,
                                           contentSize);

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

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

        return result;
    }
});
Edges {#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 size of the box model edges for the current box which is being laid out. The {{LayoutEdges}} has {{LayoutEdges/border}}, {{LayoutEdges/scrollbar}}, and {{LayoutEdges/padding}} attributes. Each of these represent the width of their respective edge. The {{LayoutEdges}} has the {{LayoutEdges/all}} attribute. This is a convenience attribute which represents the sum of the {{LayoutEdges/border}}, {{LayoutEdges/scrollbar}}, {{LayoutEdges/padding}} edges. The {{LayoutEdgeSizes}} object represents the width in CSS pixels of an edge in each of the abstract dimensions ({{LayoutEdgeSizes/inlineStart}}, {{LayoutEdgeSizes/inlineEnd}}, {{LayoutEdgeSizes/blockStart}}, {{LayoutEdgeSizes/blockEnd}}). The {{LayoutEdgeSizes/inline}}, and {{LayoutEdgeSizes/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).
    }
}
Utility Functions {#utility-functions} --------------------------------------
[Exposed=LayoutWorklet]
partial interface LayoutWorkletGlobalScope {
    double resolveInlineSize(ConstraintSpace constraintSpace,
                             StylePropertyMapReadOnly styleMap);

    double resolveBlockSize(ConstraintSpace constraintSpace,
                            StylePropertyMapReadOnly styleMap,
                            optional double contentSize);
};
Issue: Specify the behaviour of these functions. Interactions with other Modules {#interactions-with-other-modules} ================================================================== This section describes how other CSS modules interact with the CSS Layout API. Sizing {#interaction-sizing} ---------------------------- Positioning {#interaction-positioning} -------------------------------------- Overflow {#interaction-overflow} -------------------------------- Fragmentation {#interaction-fragmentation} ------------------------------------------ Alignment {#interaction-alignment} ---------------------------------- Layout {#layout} ================ This section describes how the CSS Layout API interacts with the user agent's layout engine. Layout Invalidation {#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. TODO comments. intrinsic sizes valid flag. intrinsic-sizes-valid. intrinsic-sizes-invalid. Issue: The above flag is too restrictive on user agents, change. 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. 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. 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. 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. Concepts {#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: - class constructor which is the class constructor. - layout generator function which is the layout generator function callback. - intrinsic sizes generator function which is the intrinsic sizes generator function callback. - constructor valid flag. - input properties which is a list of DOMStrings - child input properties which is a list of DOMStrings. - child display a {{ChildDisplayType}}. 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: - input properties which is a list of DOMStrings - child input properties which is a list of DOMStrings. - child display a {{ChildDisplayType}}. Layout Worklet {#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);
};
Registering A Layout {#registering-layout} ------------------------------------------
[Exposed=LayoutWorklet]
enum ChildDisplayType {
    "block",
    "normal",
};
Issue: "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: - class constructor being |layoutCtor|. - layout generator function being |layout|. - intrinsic sizes generator function being |intrinsicSizes|. - constructor valid flag being true. - input properties being |inputProperties|. - child input properties being |childInputProperties|. - child display being |childDisplay|. 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: - input properties being |inputProperties|. - child input properties being |childInputProperties|. - child display being |childDisplay|. 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.
            }
        }
    
Layout Engine {#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 {{LayoutChild/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 {{LayoutChild/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 {{LayoutChild/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.
  }
}
Performing Layout {#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 = [];
    BreakTokenOptions breakToken = null;
};

dictionary IntrinsicSizesResultOptions {
    double maxContentSize;
    double minContentSize;
};

interface IntrinsicSizes {
  readonly attribute double minContentSize;
  readonly attribute double maxContentSize;
};
Issue: Need to specify that the {{LayoutChild}} objects should remain the same between layouts so the author can store information? Not sure. ### Determining Intrinsic Sizes ### {#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 <> 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 {{LayoutWorkletGlobalScope}}s 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 {{LayoutChild/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|: - Let |intrinsicSizes|'s {{IntrinsicSizesResultOptions/minContentSize}} be the min-content size of |box|. - Let |intrinsicSizes|'s {{IntrinsicSizesResultOptions/maxContentSize}} be the max-content size of |box|.
### Generating Fragments ### {#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 <> 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 {{LayoutWorkletGlobalScope}}s 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 {{LayoutChild/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: - The inline size set to |fragment|'s {{FragmentResultOptions/inlineSize}}. - The block size set to |fragment|'s {{FragmentResultOptions/blockSize}}. - The child fragments set to |fragment|'s {{FragmentResultOptions/childFragments}} list. The ordering is important as this dictates their paint order (described in [[#layout-api-containers]]). Their position relative to the border box of the |fragment| should be based off the author specified {{Fragment/inlineOffset}} and {{Fragment/blockOffset}}. - The fragmentation break information set to |fragment|'s {{FragmentResultOptions/breakToken}}. TODO: storage of the break token.
### Utility Algorithms ### {#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 {{LayoutWorkletGlobalScope}}s. 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: - {{IntrinsicSizes/minContentSize}} being |layoutChild|'s min-content size. - {{IntrinsicSizes/maxContentSize}} being |layoutChild|'s max-content size. 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 |result| be a new {{Fragment}} with: - {{Fragment/inlineSize}} being |internalFragment|'s inline size. - {{Fragment/blockSize}} being |internalFragment|'s block size. - {{Fragment/inlineOffset}} initially set to 0. - {{Fragment/blockOffset}} initially set to 0. - {{Fragment/breakToken}} being a new {{ChildBreakToken}} representing |layoutChild|'s internal break token. 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. 4. 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").