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.
For a high level overview of this API, see the EXPLAINER.
2. Layout API Containers
A new alternative value is added
to the <display-inside> production: 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.
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 § 5.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 respect the margin property on children.
<!DOCTYPE html> < style > @ supports ( display : layout ( centering )) { . centering-layout { display : layout ( centering ); } } </ style > < div class = "centering-layout" ></ div >
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 inflow children of a layout API container can act in different ways depending on the value
of layout options' childDisplay
(set by layoutOptions
on the class).
If the value of layout options' childDisplay
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 layout options' childDisplay
is "normal"
, no blockification occurs. Instead children with a <display-outside> computed value of inline (a root inline box) will produce a single LayoutFragment
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.
-
A block-level box inside a inline-level box is inlinified I.e. its <display-outside> is set to inline.
-
A float inside a inline-level box is not taken out of flow. Instead it must be treated as inflow, and be inlinified.
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.
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 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 namespace CSS { [SameObject ]readonly attribute Worklet ; };
layoutWorklet
The LayoutWorkletGlobalScope
is the global execution context of the layoutWorklet
.
[Global =(Worklet ,LayoutWorklet ),Exposed =LayoutWorklet ]interface :
LayoutWorkletGlobalScope WorkletGlobalScope {undefined registerLayout (DOMString ,
name VoidFunction ); };
layoutCtor
if ( 'layoutWorklet' in CSS) { console. log( 'CSS Layout API available!' ); }
3.1. Concepts
This section describes internal data-structures created when registerLayout(name, layoutCtor)
is
called.
A layout definition is a struct which describes the information needed by the LayoutWorkletGlobalScope
about the author defined layout (which can be referenced by the layout() function). It consists of:
-
class constructor which is the class constructor.
-
layout function which is the layout function callback.
-
intrinsic sizes function which is the intrinsic sizes function callback.
-
constructor valid flag.
-
input properties which is a list of
DOMStrings
. -
child input properties which is a list of
DOMStrings
. -
layout options a
LayoutOptions
.
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
. -
layout options a
LayoutOptions
.
3.2. Registering A Layout
The section describes how a web developer uses registerLayout(name, layoutCtor)
to register a
layout.
dictionary {
LayoutOptions ChildDisplayType = "block";
childDisplay LayoutSizingMode = "block-like"; };
sizing enum {
ChildDisplayType , // default - "blockifies" the child boxes.
"block" , };
"normal" enum {
LayoutSizingMode , // default - Sizing behaves like block containers.
"block-like" , // Sizing is specified by the web developer. };
"manual"
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.
Each box representing a layout API container 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.
Each box representing a layout API container has a styleMap internal slot.
This is a StylePropertyMapReadOnly
which contains the properties listed in inputProperties
.
The user agent clear the styleMap internal slot for a box when:
-
The computed values of input properties for the box changes.
-
Every 1000 layout passes.
Note: The above rule exists to ensure that web developers do not rely on being able to store non-regeneratable state on the
StylePropertyMapReadOnly
object. The 1000 limit was picked as a high upper bound, this limit may improve (downwards) over time.
registerLayout( 'example' , class { static inputProperties= [ '--foo' ]; static childInputProperties= [ '--bar' ]; static layoutOptions= { childDisplay: 'normal' , sizing: 'block-like' }; async intrinsicSizes( children, edges, styleMap) { // Intrinsic sizes code goes here. } async layout( children, edges, constraints, styleMap, breakToken) { // Layout code goes here. } });
The algorithm below is run when the registerLayout(name, layoutCtor)
is called. It notifies the
user agent layout engine about the new user defined layout.
registerLayout(name, layoutCtor)
method
is called, the user agent must run the following steps:
-
If the name is an empty string, throw a TypeError and abort all these steps.
-
Let layoutDefinitionMap be
LayoutWorkletGlobalScope
's layout definitions map. -
If layoutDefinitionMap[name] exists throw a "
InvalidModificationError
"DOMException
and abort all these steps. -
Let inputProperties be an empty
sequence<DOMString>
. -
Let inputPropertiesIterable be the result of Get(layoutCtor, "inputProperties").
-
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. -
Filter inputProperties so that it only contains supported CSS properties and custom properties.
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
. -
Let childInputProperties be an empty
sequence<DOMString>
. -
Let childInputPropertiesIterable be the result of Get(layoutCtor, "childInputProperties").
-
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. -
Filter childInputProperties so that it only contains supported CSS properties and custom properties.
-
Let layoutOptionsValue be the result of Get(layoutCtor, "layoutOptions").
-
Let layoutOptions be the result of converting layoutOptionsValue to a
LayoutOptions
. If an exception is thrown, rethrow the exception and abort all these steps. -
Let prototype be the result of Get(layoutCtor, "prototype").
-
If the result of Type(prototype) is not Object, throw a TypeError and abort all these steps.
-
Let intrinsicSizesValue be the result of Get(prototype, "intrinsicSizes").
-
Let intrinsicSizes be the result of converting intrinsicSizesValue to the
Function
callback function type. Rethrow any exceptions from the conversion. -
Let layoutValue be the result of Get(prototype,
"layout"
). -
Let layout be the result of converting layoutValue to the
Function
callback function type. Rethrow any exceptions from the conversion. -
Let definition be a new layout definition with:
-
class constructor being layoutCtor.
-
layout function being layout.
-
intrinsic sizes function being intrinsicSizes.
-
constructor valid flag being true.
-
child input properties being childInputProperties.
-
input properties being inputProperties.
-
layout options being layoutOptions.
-
-
Set layoutDefinitionMap[name] to definition.
-
Queue a task to run the following steps:
-
Let documentLayoutDefinitionMap be the associated document’s document layout definitions map.
-
Let documentDefinition be a new document layout definition with:
-
child input properties being childInputProperties.
-
input properties being inputProperties.
-
layout options being layoutOptions.
-
-
If documentLayoutDefinitionMap[name] exists, run the following steps:
-
Let existingDocumentDefinition be the result of get documentLayoutDefinitionMap[name].
-
If existingDocumentDefinition is
"invalid"
, abort all these steps. -
If existingDocumentDefinition and documentDefinition are not equivalent, (that is input properties, child input properties, and layout options 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
, orlayoutOptions
.
-
-
Otherwise, set documentLayoutDefinitionMap[name] to documentDefinition.
-
3.3. Terminology
We define the following terms to be clear about which layout algorithm (formatting context) we are talking about.
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.
4. Layout API
This section describes the objects of the Layout API provided to web developers.
4.1. Layout Children
A LayoutChild
represents a inflow CSS generated box before layout has occurred. (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 LayoutFragment
s which do contain layout information.
An author cannot construct a LayoutChild
with this API, this happens at a separate stage of the
user agent rendering engine (post style resolution).
An array of LayoutChild
ren is passed into the layout/intrinsicSizes methods which represents the
children of the current box which is being laid out.
[Exposed =LayoutWorklet ]interface {
LayoutChild readonly attribute StylePropertyMapReadOnly styleMap ;Promise <IntrinsicSizes >intrinsicSizes ();Promise <LayoutFragment >layoutNextFragment (LayoutConstraintsOptions ,
constraints ChildBreakToken ); };
breakToken
The LayoutChild
has internal slot(s):
-
[[box]]
a CSS box. -
[[styleMap]]
aStylePropertyMapReadOnly
, this is the computed style for the child, it is populated with only the properties listed inchildInputProperties
.
-
[[unique id]]
the unique id of the current layout api context. This slot is used so that aLayoutChild
used outside the current layout pass is invalid.
The [[styleMap]]
may be pre-populated when the computed value for properties
listed in the in child input properties for the [[box]]
.
LayoutChild
.
registerLayout( 'example-layout-child' , class { static childInputProperties= [ '--foo' ]; async layout( children, edges, constraints, styleMap) { // An array of LayoutChildren is passed into both the layout function, // and intrinsic sizes function below. const child= children[ 0 ]; // You can query the any properties listed in "childInputProperties". const fooValue= child. styleMap. get( '--foo' ); // And perform layout! const fragment= await child. layoutNextFragment({}); } async intrinsicSizes( children, edges, styleMap) { // Or request the intrinsic size! const childIntrinsicSize= await children[ 0 ]. intrinsicSizes(); } });
A LayoutChild
could be generated by:
-
An element.
-
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.
-
LayoutChild
ren:
< style > # box :: before { content : 'hello!' ; } </ style > < div id = "box" > A block level box with text.</ div > < img src = "..." />
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.
LayoutFragment
but is from
three non-atomic inlines:
ع< span style = "color: blue" > ع</ span > ع
Note: When accessing the styleMap
the user agent can create a new StylePropertyMapReadOnly
if none exists yet.
styleMap
, on getting from a LayoutChild
this, the
user agent must perform the following steps:
-
If this’
[[styleMap]]
is null, then:-
Let box be this’
[[box]]
. -
Let definition be the result of get a layout definition.
-
Let childInputProperties be definition’s child input properties.
-
Let styleMap be a new
StylePropertyMapReadOnly
populated with only the computed values for properties listed in childInputProperties for box. -
Set this’
[[styleMap]]
internal slot to styleMap.
Note: If the user agent always pre-populates
[[styleMap]]
then this branch of the algorithm won’t be reached. -
-
Return this’
StylePropertyMapReadOnly
contained in the[[styleMap]]
internal slot.
Note: The intrinsicSizes()
method allows the web developer to query the intrinsic sizes of the LayoutChild
.
intrinsicSizes()
method is called on a LayoutChild
this, the user agent must perform the following steps:
-
Let p be a new promise.
-
Let context be the current layout’s layout API context.
-
If this’
[[unique id]]
is not equal to context’s unique id, reject p with a "InvalidStateError
"DOMException
, and abort all these steps.Note: This is to ensure that only
LayoutChild
ren passed in as arguments to either the layout or intrinsicSizes method are used. -
Let task be a new layout API work task with:
-
layout child being this.
-
task type being
"intrinsic-sizes"
. -
promise being p.
-
-
Append task to context’s work queue.
-
Return p.
Note: The layoutNextFragment()
method allows the web developer to produce a LayoutFragment
for a given LayoutChild
(the result of performing layout).
layoutNextFragment(constraints, breakToken)
method is
called on a LayoutChild
this, the user agent must perform the following steps:
-
Let p be a new promise.
-
Let context be the current layout’s layout API context.
-
If this’
[[unique id]]
is not equal to context’s unique id, reject p with a "InvalidStateError
"DOMException
, and abort all these steps.Note: This is to ensure that only
LayoutChild
ren passed in as arguments to either the layout or intrinsicSizes method are used. -
If breakToken’s {{ChildBreakToken/[[unique id]]} is not equal to context’s unique id, reject p with a "
InvalidStateError
"DOMException
, and abort all these steps. -
If context’s mode is
"intrinsic-sizes"
, reject p with a "NotSupportedError
"DOMException
.Note: This is to ensure that inside a
intrinsicSizes
callback,layoutNextFragment()
cannot be called. -
Let task be a new layout API work task with:
-
layout constraints being constraints.
-
layout child being this.
-
child break token being breakToken.
-
task type being
"layout"
. -
promise being p.
-
-
Append task to context’s work queue.
-
Return p.
4.1.1. LayoutChildren and the Box Tree
Each box has a [[layoutChildMap]]
internal slot, which is a map of LayoutWorkletGlobalScope
s to LayoutChild
ren.
Note: Get a layout child returns a LayoutChild
object for the correct LayoutWorkletGlobalScope
and creates one if it doesn’t exist yet.
-
Assert that:
-
box is currently attached to the box tree.
-
box’s containing block is a layout API container.
-
The containing block’s layout() function’s first argument is name.
-
-
Let layoutChildMap be box’s
[[layoutChildMap]]
. -
If layoutChildMap[workletGlobalScope] does not exist, run the following steps:
-
Let definition be the result of get a layout definition given name, and workletGlobalScope.
Assert that get a layout definition succeeded, and definition is not
"invalid"
. -
Let childInputProperties be definition’s child input properties.
-
Let layoutChild be a new
LayoutChild
with internal slot(s):-
[[box]]
set to box. -
[[styleMap]]
set to a newStylePropertyMapReadOnly
populated with only the computed values for properties listed in childInputProperties.
-
-
Set layoutChildMap[workletGlobalScope] to layoutChild.
-
-
Let layoutChild be the result of get layoutChildMap[workletGlobalScope].
-
Set layoutChild’s
[[unique id]]
internal slot to uniqueId. -
Return layoutChild.
When a box is inserted into the box tree the user agent may pre-populate the [[layoutChildMap]]
for all LayoutWorkletGlobalScope
s.
When a box is removed from the box tree the user agent must clear the [[layoutChildMap]]
.
The user agent must clear the [[layoutChildMap]]
internal slot every 1000 layout
passes.
Note: The above rule exists to ensure that web developers do not rely on being able to store
non-regeneratable state on the LayoutChild
object.
The 1000 limit was picked as a high upper bound, this limit may improve (downwards) over time.
-
Assert that:
-
box is currently attached to the box tree.
-
-
If box’s containing block is not a layout API container, abort all these steps.
-
Let layoutChildMap be box’s
[[layoutChildMap]]
. -
For each layoutChild in layoutChildMap:
-
layoutChild’s
[[styleMap]]
to null.
-
When the computed values of child input properties for a box changes the user agent must run the update a layout child style algorithm.
4.2. Layout Fragments
A LayoutFragment
represents a CSS fragment of a LayoutChild
after layout has occurred on
that child. This is produced by the layoutNextFragment()
method.
[Exposed =LayoutWorklet ]interface {
LayoutFragment readonly attribute double ;
inlineSize readonly attribute double ;
blockSize attribute double ;
inlineOffset attribute double ;
blockOffset readonly attribute any ;
data readonly attribute ChildBreakToken ?; };
breakToken
The LayoutFragment
has internal slot(s):
-
[[unique id]]
the unique id of the layout api context which produced this child fragment. This slot is used so that aLayoutFragment
from a previous layout pass is invalid.
The LayoutFragment
has inlineSize
and blockSize
attributes, which are set by the respective child’s layout algorithm. They represent the border
box size of the CSS fragment, and are relative to the current layout’s writing mode.
The inlineSize
and blockSize
attributes 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 LayoutFragment
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 LayoutFragment
relative to its parent’s border
box, before transform or positioning (e.g. if a fragment is relatively positioned) has
been applied.
LayoutFragment
.
registerLayout( 'example-layout-fragment' , class { async layout( children, edges, constraints, styleMap) { // You must perform layout to generate a fragment. const fragment= await child. layoutNextFragment({}); // You can query the size of the fragment produced: console. log( fragment. inlineSize); console. log( fragment. blockSize); // You can set the position of the fragment, e.g. this will set it to the // top-left corner: fragment. inlineOffset= edges. inlineStart; fragment. blockOffset= edges. blockStart; // Data may be passed from the child layout: console. log( fragment. data); // If the child fragmented, you can use the breakToken to produce the next // fragment in the chain. const nextFragment= await child. layoutNextFragment({}, fragment. breakToken); } });
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 LayoutFragment
's breakToken
specifies where the LayoutChild
last
fragmented. If the breakToken
is null the LayoutChild
wont produce any more LayoutFragment
s for that token chain. The breakToken
can be passed to the layoutNextFragment()
function to produce the next LayoutFragment
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.
4.3. Intrinsic Sizes
[Exposed =LayoutWorklet ]interface {
IntrinsicSizes readonly attribute double ;
minContentSize readonly attribute double ; };
maxContentSize
A IntrinsicSizes
object represents the min-content size and max-content size of a CSS box. It has minContentSize
and maxContentSize
attributes
which represent the border box min/max-content contribution of the LayoutChild
for the current layout. The attributes are relative to the inline direction of the current layout’s writing mode.
The minContentSize
and maxContentSize
cannot be changed. They
must not change for a LayoutChild
within the current layout pass.
< style > . child-0 { width : 380 px ; border : solid 10 px ; } . child-1 { border : solid 5 px ; } . box { display : layout ( intrinsic -sizes-example ); font : 25 px / 1 Ahem ; } </ style > < div class = "box" > < div class = "child-0" ></ div > < div class = "child-1" > XXX XXXX</ div > </ div >
registerLayout( 'intrinsic-sizes-example' , class { async intrinsicSizes( children, edges, styleMap) { const childrenSizes= await Promise. all( children. map(( child) => { return child. intrinsicSizes(); })); childrenSizes[ 0 ]. minContentSize; // 400, (380+10+10) child has a fixed size. childrenSizes[ 0 ]. maxContentSize; // 400, (380+10+10) child has a fixed size. childrenSizes[ 1 ]. minContentSize; // 100, size of "XXXX". childrenSizes[ 1 ]. maxContentSize; // 200, size of "XXX XXXX". } layout() {} });
4.4. Layout Constraints
A LayoutConstraints
object is passed into the layout method which represents the all the
constraints for the current layout to perform layout within.
[Exposed =LayoutWorklet ]interface {
LayoutConstraints readonly attribute double ;
availableInlineSize readonly attribute double ;
availableBlockSize readonly attribute double ?;
fixedInlineSize readonly attribute double ?;
fixedBlockSize readonly attribute double ;
percentageInlineSize readonly attribute double ;
percentageBlockSize readonly attribute double ?;
blockFragmentationOffset readonly attribute BlockFragmentationType ;
blockFragmentationType readonly attribute any ; };
data enum {
BlockFragmentationType ,
"none" ,
"page" ,
"column" };
"region"
The LayoutConstraints
object has availableInlineSize
and availableBlockSize
attributes. This represents the available space for the current layout to respect.
Note: Some layouts may need to produce a LayoutFragment
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 fixedInlineSize
or fixedBlockSize
are specified the current layout should produce a LayoutFragment
with a the specified size in the appropriate
direction.
The LayoutConstraints
object has percentageInlineSize
and percentageBlockSize
attributes. These represent the size that percentages
should be resolved against while performing layout.
The LayoutConstraints
has a blockFragmentationType
attribute. The current layout should produce a LayoutFragment
which fragments at the blockFragmentationOffset
if possible.
The current layout can choose not to fragment a LayoutChild
based on the blockFragmentationType
, for example if the child has a property like break-inside: avoid-page;.
LayoutConstraints
object.
// The class below is registered with a "block-like" sizingMode, and can use the fixedInlineSize, // fixedBlockSize attributes. registerLayout( 'layout-constraints-example' , class { async layout( children, edges, constraints, styleMap) { // Calculate the available size. const availableInlineSize= constraints. fixedInlineSize- edges. inline; const availableBlockSize= constraints. fixedBlockSize? constraints. fixedBlockSize- edges. inline: null ; // Web developers should resolve any percentages against the percentage sizes. const value= constraints. percentageInlineSize* 0.5 ; } });
The create a layout constraints object algorithm is used to create the LayoutConstraints
object. Depending on the sizing
it will either pre-calculate the fixedInlineSize
and fixedBlockSize
upfront.
-
If sizingMode is
"block-like"
then:-
Let fixedInlineSize be the result of calculating box’s border-box inline size (relative to box’s writing mode) exactly like block containers do.
-
Let fixedBlockSize be null if box’s block size is unable to be calculated at this stage, (e.g. block size is auto), otherwise the result of calculating box’s border-box block size exactly like block containers do.
-
Return a new
LayoutConstraints
object with:-
fixedInlineSize
, andavailableInlineSize
set to fixedInlineSize. -
percentageInlineSize
set to internalLayoutConstraints’ percentage resolution size in the inline axis (relative to box’s writing mode). -
fixedBlockSize
set to fixedBlockSize. -
availableBlockSize
set to fixedBlockSize if not null, otherwise internalLayoutConstraints’ available space in the block axis (relative to box’s writing mode). -
percentageBlockSize
set to internalLayoutConstraints’ percentage resolution size in the block axis (relative to box’s writing mode).
-
-
-
If sizingMode is
"manual"
then:-
Return a new
LayoutConstraints
object with:-
fixedInlineSize
/fixedBlockSize
set to internalLayoutConstraints’ fixed inline/block size (relative to box’s writing mode) imposed by the parent layout. Either may be null.Note: See § 5.1 Sizing for different scenarios when this can occur.
-
availableInlineSize
/availableBlockSize
set to internalLayoutConstraints’ available space. -
percentageInlineSize
/percentageBlockSize
set to internalLayoutConstraints’ percentage resolution size.
-
-
4.4.1. Constraints for Layout Children
The LayoutConstraintsOptions
dictionary represents the set of constraints which can be passed to
a LayoutChild
to produce a LayoutFragment
.
dictionary {
LayoutConstraintsOptions double ;
availableInlineSize double ;
availableBlockSize double ;
fixedInlineSize double ;
fixedBlockSize double ;
percentageInlineSize double ;
percentageBlockSize double ;
blockFragmentationOffset BlockFragmentationType = "none";
blockFragmentationType any ; };
data
Note: The translate a LayoutConstraintsOptions to internal constraints describes how to convert
a LayoutConstraintsOptions
object into a user agents internal representation.
-
Let the available space in the inline direction (with respect to the current layout, be the result of:
-
If options’
availableInlineSize
is not null, andavailableInlineSize
is greater than zero, let the result beavailableInlineSize
. -
Otherwhise, let the result be zero.
-
-
Let the available space in the block direction (with respect to the current layout), be the result of:
-
If options’
availableBlockSize
is not null, andavailableBlockSize
is greater than zero, let the result beavailableBlockSize
. -
Otherwhise, let the result be zero.
-
-
Let the override size in the inline direction (with respect to the current layout, be the result of:
-
Let the result be options’
fixedInlineSize
.
Note: If the
fixedInlineSize
is null, no override size is applied. -
-
Let the override size in the block direction (with respect to the current layout, be the result of:
-
Let the result be options’
fixedBlockSize
.
Note: If the
fixedBlockSize
is null, no override size is applied. -
-
Let the percentage resultion size in the inline direction (with respect to the current layout, be the result of:
-
If options’
percentageInlineSize
is not null, andpercentageInlineSize
is greater than zero, let the result bepercentageInlineSize
. -
If options’
availableInlineSize
is not null, andavailableInlineSize
is greater than zero, let the result beavailableInlineSize
. -
Otherwhise, let the result be zero.
-
-
Let the percentage resultion size in the block direction (with respect to the current layout, be the result of:
-
If options’
percentageBlockSize
is not null, andpercentageBlockSize
is greater than zero, let the result bepercentageBlockSize
. -
If options’
availableBlockSize
is not null, andavailableBlockSize
is greater than zero, let the result beavailableBlockSize
. -
Otherwhise, let the result be zero.
-
-
If the child layout is a layout API container, then let the store the data (passed by
data
) be the result of:-
Invoking StructuredSerializeForStorage on options’
data
.
-
LayoutConstraintsOptions
dictionary.
// The class below is registered with a "block-like" sizingMode, and can use the // fixedInlineSize, fixedBlockSize attributes. registerLayout( 'child-layout-constraints-example' , class { async layout( children, edges, constraints, styleMap) { // The call below gives the child an "available" space. It will try and // fit within this. const fragment= children[ 0 ]. layoutNextFragment({ availableInlineSize: 100 , availableBlockSize: 200 , }); // The call below gives the child a "fixed" size, it will be forced to // this size ignoring any style set. const fragment= children[ 0 ]. layoutNextFragment({ fixedInlineSize: 20 , fixedBlockSize: 30 , }); } });
4.5. Breaking and Fragmentation
A LayoutChild
can produce multiple LayoutFragment
s. 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 layout options' childDisplay
(set by layoutOptions
) is "normal"
.
[Exposed =LayoutWorklet ]interface {
ChildBreakToken readonly attribute BreakType ;
breakType readonly attribute LayoutChild ; }; [
child Exposed =LayoutWorklet ]interface {
BreakToken readonly attribute FrozenArray <ChildBreakToken >;
childBreakTokens readonly attribute any ; };
data dictionary {
BreakTokenOptions sequence <ChildBreakToken >;
childBreakTokens any =
data null ; };enum {
BreakType ,
"none" ,
"line" ,
"column" ,
"page" };
"region"
The ChildBreakToken
has internal slot(s):
-
[[unique id]]
the unique id of the layout api context which produced this child break token. This slot is used so that aChildBreakToken
from a previous layout pass is invalid.
A subsequent LayoutFragment
is produced by using the previous LayoutFragment
's breakToken
. This tells the child layout to produce a LayoutFragment
starting at the point encoded in the ChildBreakToken
.
4.6. Edges
A LayoutEdges
object is passed into the layout method. This represents the sum of all the box
model edges (border, scrollbar, padding), for the current box which is being laid out.
[Exposed =LayoutWorklet ]interface {
LayoutEdges readonly attribute double ;
inlineStart readonly attribute double ;
inlineEnd readonly attribute double ;
blockStart readonly attribute double ; // Convenience attributes for the sum in one direction.
blockEnd readonly attribute double ;
inline readonly attribute double ; };
block
Each of the accessors 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 LayoutEdges
object are convenience
attributes which represent the sum in that direction.
LayoutEdges
could contain.
< style > . container { width : 50 px ; height : 50 px ; } . box { display : layout ( box -edges ); padding : 10 % ; border : solid 2 px ; overflow-y : scroll ; } </ style > < div class = "container" > < div class = "box" ></ div > </ div >
registerLayout( 'box-edges' , class { async layout( children, edges, constraints, styleMap, breakToken) { edges. inlineStart; // 2 + 5 (as 10% * 50px = 5px). edges. blockEnd; // 7 (2 + 5) edges. inlineEnd; // UA-dependent, due to scrollbar. // Could be 2 + 5 + 0 or 2 + 5 + 16 for example. edges. block; // 14 (2 + 5 + 5 + 2). } }
5. Interactions with other Modules
This section describes how other CSS modules interact with the CSS Layout API.
5.1. Sizing
User agents must use the LayoutConstraints
object 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 fixedInlineSize
and fixedBlockSize
attributes to do so.
The layout API container can be passed size information in different ways depending on the value
of layout options' sizing
(set by layoutOptions
on the class).
If the value of layout options' sizing
is "block-like"
, then the LayoutConstraints
passed to the layout API container:
-
Must calculate and set
fixedInlineSize
based off the rules specified in [css-sizing-3] and the formatting context in which it participates, e.g.-
As a block-level box in a block formatting context, it is sized like a block box that establishes a formatting context, with an auto inline size calculated as for non-replaced block boxes.
-
As an inline-level box in an inline formatting context, it is sized as an atomic inline-level box (such as an inline-block).
-
-
Must calculate and set
fixedBlockSize
based off the rules specified in [css-sizing-3], and the formatting context in which it participates. If the layout API container has an auto block size, and cannot be determined ahead of time,fixedBlockSize
must be set tonull
.
If the value of layout options' sizing
is "manual"
, then the user-agent must not pre-calculate fixedInlineSize
and/or fixedBlockSize
ahead of time,
except when it is being forced to a particular size by the formatting context in which it
participates, for example:
-
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
fixedInlineSize
to the stretch-fit inline size.
< style > # container { width : 100 px ; height : 100 px ; box-sizing : border-box ; padding : 5 px ; } # layout-api { display : layout ( foo ); margin : 0 20 px ; } </ style > < div id = "container" > < div id = "layout-api" ></ div > </ div >
5.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 § 5.1 The Width of Absolutely-Positioned, Non-Replaced Elements, CSS Positioned Layout 3 § 5.3 The Height Of Absolutely Positioned, Non-Replaced Elements), and set the appropriate fixedInlineSize
and fixedBlockSize
.
< style > # container { position : relative ; width : 100 px ; height : 100 px ; } # layout-api { display : layout ( foo ); top : 10 px ; bottom : 10 px ; left : 10 px ; right : 10 px ; position : absolute ; } </ style > < div id = "container" > < div id = "layout-api" ></ div > </ div >
5.2. Positioning
All positioning in this level of the specification is handled by the user agent.
As a result:
-
Out-of-flow children do not appear as
LayoutChild
ren. -
Layout API containers establish containing blocks exactly like block containers do. [CSS21]
-
The
inlineOffset
andblockOffset
represent the position of the fragment before any positioning and transforms have occured. -
The static position of an absolutely-positioned child of a layout API container is set to the inline-start, block-start padding edge of the layout API container. Auto margins are treated as zero for the child.
-
"child-relative" would be the only child passed to the author’s layout. If it was positioned at (
inlineOffset
= 20
,blockOffset
= 30
), its final position would be (25
,40
) as the relative positioning was handled by the user agent. -
"child-absolute" would not appear as a
LayoutChild
, and instead would be laid out and positioned by the user agent. -
The examples above also apply in a similar way to sticky and fixed positioned children.
< style > # container { display : layout ( foo ); position : relative ; /* container is a containing block */ width : 100 px ; height : 100 px ; } # child-relative { position : relative ; left : 5 px ; top : 10 px ; } </ style > < div id = "container" > < div id = "child-relative" ></ div > < div id = "child-absolute" ></ div > </ div >
5.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.
5.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.
5.5. Alignment
The first/last baseline sets of a layout API container is generated exactly like block
containers do (see CSS Box Alignment 3 § 9.1 Determining the Baselines of a Box). Except that the order of the in-flow children
should be determined by the in which they are returned form the layout method (via childFragments
) instead of the document order.
To query baseline information from a LayoutChild
.
const fragment= await child. layoutNextFragment({ fixedInlineSize: availableInlineSize, baselineRequests: [ 'alphabetic' , 'middle' ], }); fragment. baselines. get( 'alphabetic' ) === 25 /* or something */ ;
To produce baseline information for a parent layout:
registerLayout( 'baseline-producing' , class { async layout( children, edges, constraints, styleMap) { const result= { baselines: {}}; for ( let baselineRequestof constraints. baselineRequests) { // baselineRequest === 'alphabetic', or something else. result. baselines[ baselineRequest] = 25 ; } return result; } });
6. Layout
This section describes how the CSS Layout API interacts with the user agent’s layout engine.
6.1. Processing Model
A layout API work task is a struct which describes the information needed by the user agent layout engine to perform layout work. It consists of:
-
layout constraints a
LayoutConstraintsOptions
. -
layout child a
LayoutChild
. -
child break token a
ChildBreakToken
. -
task type which is either
"layout"
, or"intrinsic-sizes"
-
promise a promise object.
A layout API context is a struct which describes the information needed by the current layout to produce either a fragment or determine the intrinsic-sizes for a box. It consits of:
-
work queue which is a list of layout API work tasks. The user agent will alternate between processing these tasks, and running the microtask queue.
-
unique id a internal unique identifier. This is used for determining that objects exposed to the web developer are only used within the correct layout pass. E.g.
LayoutFragment
s returned in theFragmentResultOptions
dictionary belong to the current layout pass. -
mode which is either
"layout"
, or"intrinsic-sizes"
. This is used for determining what the user agent layout engine is producing, and if a call tolayoutNextFragment()
is valid.
-
Return a new layout API context with:
-
work queue being a new empty list.
-
unique id being a unique id.
-
mode being mode.
-
6.2. Performing Layout
The section describes how a user agent calls the web developer defined layout to produces intrinsic sizes, and fragments.
// This is the final return value from the author defined layout() method.dictionary {
FragmentResultOptions double = 0;
inlineSize double = 0;
blockSize double = 0;
autoBlockSize sequence <LayoutFragment >= [];
childFragments any =
data null ;BreakTokenOptions =
breakToken null ; }; [Exposed =LayoutWorklet ]interface {
FragmentResult constructor (optional FragmentResultOptions = {});
options readonly attribute double inlineSize ;readonly attribute double blockSize ; };dictionary {
IntrinsicSizesResultOptions double ;
maxContentSize double ; };
minContentSize
The FragmentResult
has internal slot(s):
-
[[box]]
a CSS box. -
[[inline size]]
the inline size of the resulting fragment. -
[[block size]]
the block size of the resulting fragment. -
[[child fragments]]
the list of child fragments. -
[[data]]
some optional serialized data. -
[[internal break token]]
an internal representation of the break information for this fragment. -
[[unique id]]
the unique id of the current layout api context. This slot is used so that aFragmentResult
used outside the current layout pass is invalid.
The web developer defined layout method can return either a FragmentResultOptions
or a FragmentResult
. The FragmentResult
can be used for determining the final size of the
fragment or detecting if the provided FragmentResultOptions
would result in triggering a
fallback to flow layout.
FragmentResult
instead of just returning the FragmentResultOptions
object.
registerLayout( 'feature-detection' , class { async layout( children, edges, constraints, styleMap, breakToken) { let result; try { result= new FragmentResult({ childFragments: [], autoBlockSize: 100 }); } catch ( e) { // The above call may throw, if the dictionary was just returned, it // would fallback to flow layout. } // The web developer can test what size the fragment will be. result. blockSize; // Instead of returning the dictionary, we can just return this object. return result; } }
inlineSize
, on getting from a FragmentResult
this,
the user agent must perform the following steps:
-
Return this’
[[inline size]]
internal slot.
blockSize
, on getting from a FragmentResult
this,
the user agent must perform the following steps:
-
Return this’
[[block size]]
internal slot.
FragmentResult(options)
constructor is called,
the user agent must perform the following steps:
-
Let context be the current layout’s layout API context.
-
Return the result of construct a fragment result given context, and options.
Note: The construct a fragment result algorithm performs a series of validation checks (the web developer isn’t using an object from a previous invocation, and determines the final size of the resulting fragment.
-
Let uniqueId be context’s unique id.
-
Let box be the current layout’s box.
-
Let breakTokenOptions be options’s
breakToken
. -
For each childFragment in options’s
childFragments
, perform the following stubsteps:-
If childFragment’s
[[unique id]]
internal slot is not equal to uniqueId, then throw a TypeError, and abort all these steps.
-
-
For each childBreakToken in breakTokenOptions’s
childBreakTokens
, perform the following stubsteps:-
If childBreakToken’s
[[unique id]]
internal slot is not equal to uniqueId, then throw a TypeError, and abort all these steps.
-
-
If sizingMode is
"block-like"
:-
Then:
-
Let inlineSize be the result of calculating box’s border-box inline size (relative to box’s writing mode) exactly like block containers do.
-
Let blockSize be the result of calculating box’s border-box block size (relative to box’s writing mode) exactly like block containers do, given fragment’s
autoBlockSize
as the "intrinsic block size".
-
-
Otherwise (sizingMode is
"manual"
):-
Let inlineSize be fragment’s
inlineSize
. -
Let blockSize be fragment’s
blockSize
.
-
-
-
Let clonedData be the result of invoking StructuredSerializeForStorage on options’s
data
. -
Let clonedBreakTokenData be the result of invoking StructuredSerializeForStorage on breakTokenOptions’s
data
. -
Let internalBreakToken be the internal representation of the fragmentation break containing clonedBreakTokenData, and breakTokenOptions.
-
Return a new
FragmentResult
with:-
[[box]]
being box. -
[[inline size]]
being inlineSize. -
[[block size]]
being blockSize. -
[[child fragments]]
being options’schildFragments
. -
[[data]]
being clonedData. -
[[internal break token]]
being internalBreakToken. -
[[unique id]]
being uniqueId.
-
6.2.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 arbitrary number of previous invocations to reuse.
-
Let layoutFunction be the layout() for the computed value of <display-inside> for box.
-
Let name be the first argument of the layoutFunction.
-
Let documentDefinition be the result of get a document layout definition given name.
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. -
Let workletGlobalScope be a
LayoutWorkletGlobalScope
from the layoutWorklet
's global scopes, following the rules defined in § 6.2.3 Global Scope Selection.The user agent may also create a worklet global scope at this time, given the layout
Worklet
. -
Run invoke an intrinsic sizes callback given name, box, childBoxes, and workletGlobalScope optionally in parallel.
Note: If the user agent runs invoke an intrinsic sizes callback on a thread in parallel, it should select a layout worklet global scope which can be used on that thread.
-
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.
-
Let layoutInstance be the result of get a layout class instance given box, definition, workletGlobalScope.
If get a layout class instance returned failure, let the box fallback to the flow layout and abort all these steps.
-
Let inputProperties be definition’s input properties.
-
Let children be a new list.
-
For each childBox in childBoxes perform the following substeps:
-
Let layoutChild be the result of get a layout child given workletGlobalScope, name, childBox, and context’s unique id.
-
Append layoutChild to children.
-
-
Let edges be a new
LayoutEdges
populated with the computed value for all the box model edges for box. -
Let styleMap be the result of get a style map given box, and inputProperties.
-
At this stage the user agent may re-use the intrinsic sizes from a previous invocation if children, edges, and styleMap are equivalent to that previous invocation. If so let the intrinsic sizes the cached intrinsic sizes and abort all these steps.
-
Let context be the result of create a layout API context given
"intrinsic-sizes"
. -
Let intrinsicSizesFunction be definition’s intrinsic sizes function.
-
Let value be the result of invoking intrinsicSizesFunction with « children, edges, styleMap » and "`rethrow`", and with layoutInstance as the callback this value.
If an exception is thrown the let box fallback to the flow layout and abort all these steps.
-
If value is a promise:
-
Then:
-
Let intrinsicSizesValue be the result of run a work queue given value, and context’s work queue.
If run a work queue returns failure, let the box fallback to the flow layout and abort all these steps.
-
-
Otherwise:
-
Let intrinsicSizesValue be value.
-
-
-
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. -
Set the intrinsic sizes of box:
-
Let intrinsicSizes’s
minContentSize
be the min-content size of box. -
Let intrinsicSizes’s
maxContentSize
be the max-content size of box.
-
6.2.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 arbitrary number of previous invocations to reuse.
-
Let layoutFunction be the layout() for the computed value of <display-inside> for box.
-
Let name be the first argument of the layoutFunction.
-
Let documentDefinition be the result of get a document layout definition given name.
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. -
Let workletGlobalScope be a
LayoutWorkletGlobalScope
from the layoutWorklet
's global scopes, following the rules defined in § 6.2.3 Global Scope Selection.The user agent may also create a worklet global scope at this time, given the layout
Worklet
. -
Run invoke a layout callback given name, box, childBoxes, internalLayoutConstraints, internalBreakToken, and workletGlobalScope optionally in parallel.
Note: If the user agent runs invoke a layout callback on a thread in parallel, it should select a layout worklet global scope which can be used on that thread.
-
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.
-
Let layoutInstance be the result of get a layout class instance given box, definition, workletGlobalScope.
If get a layout class instance returned failure, let the box fallback to the flow layout and abort all these steps.
-
Let context be the result of create a layout API context given
"layout"
. -
Let sizingMode be definition’s layout options'
sizing
property. -
Let inputProperties be definition’s input properties.
-
Let children be a new list.
-
For each childBox in childBoxes perform the following substeps:
-
Let layoutChild be the result of get a layout child given workletGlobalScope, name, childBox, and context’s unique id.
-
Append layoutChild to children.
-
-
Let edges be a new
LayoutEdges
populated with the computed value for all the box model edges for box. -
Let layoutConstraints be the result of create a layout constraints object given internalLayoutConstraints, box, and sizingMode.
-
Let styleMap be the result of get a style map given box, and inputProperties.
-
Let breakToken be a new
BreakToken
populated with the appropriate information from internalBreakToken.If internalBreakToken is null, let breakToken be null.
-
At this stage the user agent may re-use a fragment from a previous invocation if children, styleMap, layoutConstraints, breakToken are equivalent to that previous invocation. If so let the fragment output be that cached fragment and abort all these steps.
-
Let layoutFunction be definition’s layout function.
-
Let value be the result of invoking layoutFunction with « children, edges, layoutConstraints, styleMap, breakToken », and with layoutInstance as the callback this value.
If an exception is thrown the let box fallback to the flow layout and abort all these steps.
-
If value is a promise:
-
Then:
-
Let fragmentResultValue be the result of run a work queue given value.
If run a work queue returns failure, let the box fallback to the flow layout and abort all these steps.
-
-
Otherwise:
-
Let fragmentResultValue be value.
-
-
-
If fragmentResultValue is a platform object:
-
Then:
-
Let fragmentResult be the result converting fragmentResultValue to a
FragmentResult
.If an exception is thrown, let box fallback to the flow layout and abort all these steps.
-
-
Otherwise:
-
Let fragmentResultOptions be the result of converting fragmentResultValue to a
FragmentResultOptions
.If an exception is thrown, let box fallback to the flow layout and abort all these steps.
-
Let fragmentResult be the result of construct a fragment result given fragmentResultOptions.
If an exception is thrown, let box fallback to the flow layout and abort all these steps.
-
-
-
Return an internal representation of a fragment with:
-
The inline size set to fragmentResult’s
[[inline size]]
. -
The block size set to fragmentResult’s
[[inline size]]
. -
The child fragments set to fragmentResult’s
[[child fragments]]
.The ordering is important as this dictates their paint order (described in § 2 Layout API Containers). Their position relative to the border box of the fragment should be based off the author specified
inlineOffset
andblockOffset
. -
The fragmentation break information set to fragmentResult’s
[[internal break token]]
.
-
6.2.3. Global Scope Selection
When the user agent needs to select a LayoutWorkletGlobalScope
from the layout Worklet
's global scopes list it must:
-
Select from at least two
LayoutWorkletGlobalScope
s, unless the user agent is under memory constraints. -
Not re-use the same
LayoutWorkletGlobalScope
more than 1000 times in a row.Note: The 1000 limit was picked as a high upper bound, this limit may improve (downwards) over time.
Note: These rules exist to ensure that authors do not rely on being able to store state on the global object or non-regeneratable state on the class. See the discussion in the worklets specification about code idempotence.
6.2.4. Utility Algorithms
The section specifies algorithms common to the determine the intrinsic sizes and generate a fragment algorithms.
Note: Get a document layout definition returns a document layout definition from the owning document.
-
Let documentLayoutDefinitionMap be the associated document’s document layout definitions map.
-
If documentLayoutDefinitionMap[name] does not exist, return failure and abort all these steps.
-
Return the result of get documentLayoutDefinitionMap[name].
Note: Get a layout definition returns a layout definition for a given LayoutWorkletGlobalScope
, it the desired definition doesn’t exist it will "invalidate" the document layout definition, (so that the layout can’t be used again), and return failure.
-
Let layoutDefinitionMap be workletGlobalScope’s layout definitions map.
-
If layoutDefinitionMap[name] does not exist, run the following steps:
-
Queue a task to run the following steps:
-
Let documentLayoutDefinitionMap be the associated document’s document layout definition map.
-
Set documentLayoutDefinitionMap[name] to
"invalid"
. -
The user agent should log an error to the debugging console stating that a class wasn’t registered in all
LayoutWorkletGlobalScope
s.
-
-
Return failure, and abort all these steps.
-
-
Return the result of get layoutDefinitionMap[name].
Note: Get a layout class instance returns an instance of the web developer provided class.
(Registered in registerLayout()
). If one isn’t present yet, it will create a new one. This
algorithm may fail, as the constructor may throw an exception.
-
Let layoutClassInstanceMap be box’s layout class instances map.
-
Let layoutInstance be the result of get layoutClassInstanceMap[workletGlobalScope]. If layoutInstance is null, run the following steps:
-
If the constructor valid flag on definition is false, then return failure and abort all these steps.
-
Let layoutCtor be the class constructor on definition.
-
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.
-
Set layoutClassInstanceMap[workletGlobalScope] to layoutInstance.
-
-
Return layoutInstance.
-
If box’s styleMap is null, then:
-
Let styleMap be a new
StylePropertyMapReadOnly
populated with only the computed values for properties listed in inputProperties for box. -
Set box’s styleMap internal slot to styleMap.
-
-
Return box’s
StylePropertyMapReadOnly
contained in the styleMap internal slot.
Run a work queue is designed to allow user agents to work in both a single threaded, and multi-threaded environment.
Note: Run a work queue processes layout api work tasks enqueued with intrinsicSizes()
and layoutNextFragment()
. It will continue
processing tasks until the promise from the web developers layout or intrinsicSizes method is
resolved, or the queue is empty after running the microtask queue.
The result of running the queue will either be the result of the layout or intrinsicSizes method, or failure.
-
If promise is not a promise, return failure.
-
While workQueue is not empty, and promise is pending:
-
For each task in workQueue:
-
Let layoutChild be task’s layout child.
-
Let childPromise be task’s promise.
-
If task’s task type is
"layout"
,-
Then queue a task, or run synchronously, the following substeps:
-
Let childConstraints be task’s layout constraints.
-
Let childBreakToken be task’s child break token.
-
Let targetRealm be layoutChild’s Realm.
-
Let internalFragment be the result of the user agent producing a fragment based on box, childConstraints, and childBreakToken.
Invoking translate a LayoutConstraintsOptions to internal constraints given childConstraints, must be run to translate the given
LayoutConstraintsOptions
into the internal constraints for the user agent’s layout engine. -
Let fragment be a new
LayoutFragment
with:-
inlineSize
being internalFragment’s inline size relative to the current layout’s writing mode. -
blockSize
being internalFragment’s block size relative to the current layout’s writing mode. -
inlineOffset
initially set to 0. -
blockOffset
initially set to 0. -
breakToken
being a newChildBreakToken
representing internalFragment’s internal break token, if any. -
If internalFragment has a clonedData object stored with it, let
data
being the result of StructuredDeserialize(clonedData, targetRealm), otherwise null.
-
-
Resolve childPromise with fragment.
-
-
Otherwise queue a task, or run synchronously, the following substeps:
-
Let internalIntrinsicSizes be the result of the user agent calculating the border box min/max content contribution of box.
-
Let intrinsicSizes be a new
IntrinsicSizes
with:-
minContentSize
being internalIntrinsicSizes’ border box min-content contribution, relative to the current layout’s writing mode. -
maxContentSize
being internalIntrinsicSizes’s border box max-content contribution, relative to the current layout’s writing mode.
-
-
Resolve childPromise with intrinsicSizes.
-
-
-
-
Wait (optionally in parallel) for all of the above tasks to complete.
Note: If the tasks were perform synchronously, then this step is a no-op.
-
Empty workQueue.
-
-
If promise isn’t fulfilled (it is pending, or got rejected), return failure.
-
Return the fulfilled value of promise.
7. Examples
registerLayout( 'block-like' , class { async intrinsicSizes( children, edges, styleMap) { const childrenSizes= await Promise. all( children. map(( child) => { return child. intrinsicSizes(); })); const maxContentSize= childrenSizes. reduce(( max, childSizes) => { return Math. max( max, childSizes. maxContentSize); }, 0 ) + edges. inline; const minContentSize= childrenSizes. reduce(( max, childSizes) => { return Math. max( max, childSizes. minContentSize); }, 0 ) + edges. inline; return { maxContentSize, minContentSize}; } async layout( children, edges, constraints, styleMap) { // Determine our (inner) available size. const availableInlineSize= constraints. fixedInlineSize- edges. inline; const availableBlockSize= constraints. fixedBlockSize? constraints. fixedBlockSize- edges. block: null ; const childFragments= []; const childConstraints= { availableInlineSize, availableBlockSize}; const childFragments= await Promise. all( children. map(( child) => { return child. layoutNextFragment( childConstraints); })); let blockOffset= edges. blockStart; for ( let fragmentof childFragments) { // Position the fragment in a block like manner, centering it in the // inline direction. fragment. blockOffset= blockOffset; fragment. inlineOffset= Math. max( edges. inlineStart, ( availableInlineSize- fragment. inlineSize) / 2 ); blockOffset+= fragment. blockSize; } const autoBlockSize= blockOffset+ edges. blockEnd; return { autoBlockSize, childFragments, }; } });
registerLayout( 'flex-distribution-like' , class { async intrinsicSizes( children, edges, styleMap) { const childrenSizes= await Promise. all( children. map(( child) => { return child. intrinsicSizes(); })); const maxContentSize= childrenSizes. reduce(( sum, childSizes) => { return sum+ childSizes. maxContentSize; }, 0 ) + edges. inline; const minContentSize= childrenSizes. reduce(( max, childSizes) => { return sum+ childSizes. minContentSize; }, 0 ) + edges. inline; return { maxContentSize, minContentSize}; } async layout( children, edges, constraints, styleMap) { // Determine our (inner) available size. const availableInlineSize= constraints. fixedInlineSize- edges. inline; const availableBlockSize= constraints. fixedBlockSize? constraints. fixedBlockSize- edges. block: null ; const childConstraints= { availableInlineSize, availableBlockSize}; const unconstrainedChildFragments= await Promise. all( children. map(( child) => { return child. layoutNextFragment( childConstraints); })); const unconstrainedSizes= []; const totalSize= unconstrainedChildFragments. reduce(( sum, fragment, i) => { unconstrainedSizes[ i] = fragment. inlineSize; return sum+ fragment. inlineSize; }, 0 ); // Distribute spare space between children. const remainingSpace= Math. max( 0 , inlineSize- totalSize); const extraSpace= remainingSpace/ children. length; const childFragments= await Promise. all( children. map(( child, i) => { return child. layoutNextFragment({ fixedInlineSize: unconstrainedSizes[ i] + extraSpace, availableBlockSize}); })); // Position the fragments. let inlineOffset= 0 ; let maxChildBlockSize= 0 ; for ( let fragmentof childFragments) { fragment. inlineOffset= inlineOffset; fragment. blockOffset= edges. blockStart; inlineOffset+= fragment. inlineSize; maxChildBlockSize= Math. max( maxChildBlockSize, fragment. blockSize); } return { autoBlockSize: maxChildBlockSize+ edges. block, childFragments, }; } });
This example also demonstrates using the previous breakToken
of a LayoutFragment
to produce the next fragment for the LayoutChild
.
It also demonstrates using the BreakToken
to respect the LayoutConstraints
' 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 layoutOptions= { childDisplay: 'normal' }; static inputProperties= [ '--indent' , '--indent-lines' ]; async layout( children, edges, constraints, styleMap, breakToken) { // Determine our (inner) available size. const availableInlineSize= constraints. fixedInlineSize- edges. inline; const availableBlockSize= constraints. fixedBlockSize? constraints. fixedBlockSize- edges. block: null ; // Detrermine the number of lines to indent, and the indent amount. const indent= resolveLength( constraints, styleMap. get( '--indent' )); let lines= styleMap. get( '--indent-lines' ). value; const childFragments= []; let childBreakToken= null ; if ( breakToken) { childBreakToken= breakToken. childBreakTokens[ 0 ]; // Remove all the children we have already produced fragments for. children. splice( 0 , children. indexOf( childBreakToken. child)); } let blockOffset= edges. 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 childConstraints= { availableInlineSize: childAvailableInlineSize, availableBlockSize, percentageInlineSize: availableInlineSize, blockFragmentationType: constraints. blockFragmentationType, }; const fragment= await child. layoutNextFragment( childConstraints, childBreakToken); childFragments. push( fragment); // Position the fragment. fragment. inlineOffset= shouldIndent? edges. inlineStart+ indent: edges. inlineStart; fragment. blockOffset= blockOffset; blockOffset+= fragment. blockSize; // Check if we have gone over the block fragmentation limit. if ( constraints. blockFragmentationType!= 'none' && blockOffset> constraints. 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. blockEnd; // Return our fragment. const result= { autoBlockSize, childFragments: childFragments, } if ( childBreakToken) { result. breakToken= { childBreakTokens: [ childBreakToken], }; } return result; } });
8. Security Considerations
There are no known security issues introduced by these features.
9. Privacy Considerations
There are no known privacy issues introduced by these features.