> 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.
Note: In the example below "inline-span" would be represented as a single {{LayoutChild}} with
both "block" and "float" being
atomic inlines.
<span id="inline-span">
Text
<div id="block"></div>
<div id="float"></div>
Text
</span>
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 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 {
void registerLayout(DOMString name, VoidFunction layoutCtor);
};
Web developers can feature detect by:
if ('layoutWorklet' in CSS) {
console.log('CSS Layout API available!');
}
Concepts {#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}}.
Registering A Layout {#registering-layout}
------------------------------------------
[Exposed=LayoutWorklet]
dictionary LayoutOptions {
ChildDisplayType childDisplay = "block";
LayoutSizingMode sizing = "block-like";
};
[Exposed=LayoutWorklet]
enum ChildDisplayType {
"block",
"normal",
};
[Exposed=LayoutWorklet]
enum LayoutSizingMode {
"block-like",
"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=].
Note: The shape of the class should be:
registerLayout('example', class {
static get inputProperties() { return ['--foo']; }
static get childrenInputProperties() { return ['--bar']; }
static get layoutOptions() {
return {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 rendering engine about the new user defined layout.
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|] [=map/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.
7. 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
.
8. Let |childInputProperties| be an empty sequence<DOMString>
.
9. Let |childInputPropertiesIterable| be the result of [=Get=](|layoutCtor|,
"childInputProperties").
10. 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.
11. Filter |childInputProperties| so that it only contains [=supported CSS properties=] and [=custom
properties=].
12. Let |layoutOptionsValue| be the result of [=Get=](|layoutCtor|, "layoutOptions").
13. Let |layoutOptions| be the result of [=converting=] |layoutOptionsValue| to a
{{LayoutOptions}}. If an exception is [=thrown=], rethrow the exception 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 |intrinsicSizesValue| be the result of [=Get=](|prototype|, "intrinsicSizes").
17. Let |intrinsicSizes| be the result of [=converting=] |intrinsicSizesValue| to the
[=Function=] [=callback function=] type. Rethrow any exceptions from the conversion.
18. Let |layoutValue| be the result of [=Get=](|prototype|, "layout"
).
19. Let |layout| be the result of [=converting=] |layoutValue| to the [=Function=] [=callback
function=] type. Rethrow any exceptions from the conversion.
20. 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.
- [=layout definition/child input properties=] being |childInputProperties|.
- [=layout definition/input properties=] being |inputProperties|.
- [=layout defintion/layout options=] being |layoutOptions|.
21. [=map/Set=] |layoutDefinitionMap|[|name|] to |definition|.
22. [=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:
- [=document layout definition/child input properties=] being |childInputProperties|.
- [=document layout definition/input properties=] being |inputProperties|.
- [=document layout definition/layout options=] being |layoutOptions|.
3. If |documentLayoutDefinitionMap|[|name|] [=map/exists=], run the following steps:
1. Let |existingDocumentDefinition| be the result of [=map/get=]
|documentLayoutDefinitionMap|[|name|].
2. If |existingDocumentDefinition| is "invalid"
, abort all these steps.
3. If |existingDocumentDefinition| and |documentDefinition| are not equivalent, (that is
[=document layout definition/input properties=], [=document layout definition/child
input properties=], and [=document layout definition/layout options=] are
different), then:
[=map/Set=] |documentLayoutDefinitionMap|[|name|] to "invalid"
.
Log an error to the debugging console stating that the same class was registered
with different inputProperties
, childInputProperties
, or
layoutOptions
.
4. Otherwise, [=map/set=] |documentLayoutDefinitionMap|[|name|] to |documentDefinition|.
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();
LayoutFragmentRequest layoutNextFragment(LayoutConstraintsOptions constraints, ChildBreakToken breakToken);
};
The {{LayoutChild}} has internal slot(s):
- \[[box]] a CSS box.
- \[[styleMap]] a {{StylePropertyMapReadOnly}}, this is the
computed style for the child, it is populated with only the properties listed in
childInputProperties
.
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 {{LayoutFragment}}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).
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 {{LayoutFragment}} but is from
three non-
atomic inlines:
ع<span style="color: blue">ع</span>ع
An array of {{LayoutChild}}ren is passed into the layout method which represents the children of the
current box which is being laid out.
The styleMap, on getting from a {{LayoutChild}} |this|, the
user agent must perform the following steps:
1. Return |this|' {{StylePropertyMapReadOnly}} contained in the {{[[styleMap]]}} internal slot.
When the layoutNextFragment(|constraints|, |breakToken|) method is
called on a {{LayoutChild}} |this|, the user agent must perform the following steps:
1. Let |request| be a new {{LayoutFragmentRequest}} with internal slot(s):
- {{LayoutFragmentRequest/[[layoutChild]]}} set to |this|.
- {{[[layoutConstraints]]}} set to |constraints|.
- {{[[breakToken]]}} set to |breakToken|.
2. Return |request|.
When the intrinsicSizes() method is called on a {{LayoutChild}}
|this|, the user agent must perform the following steps:
1. Let |request| be a new {{IntrinsicSizesRequest}} with internal slot(s):
- {{IntrinsicSizesRequest/[[layoutChild]]}} set to |this|.
2. Return |request|.
Note: Both {{LayoutChild/layoutNextFragment()}} and {{LayoutChild/intrinsicSizes()}} don't
synchronously run. See [[#request-objects]] for a full description.
### LayoutChildren and the Box Tree ### {#layout-child-box-tree}
Each box has a \[[layoutChildMap]] internal slot, which is a
map of {{LayoutWorkletGlobalScope}}s to {{LayoutChild}}ren.
When the user agent wants to
get a layout child given |workletGlobalScope|, |name|, and
|box|, it
must run the following steps:
1. 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|.
2. Let |layoutChildMap| be |box|'s {{[[layoutChildMap]]}}.
3. If |layoutChildMap|[|workletGlobalScope|] does not
exist, run the following
steps:
1. 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"
.
2. Let |childInputProperties| be |definition|'s
child input
properties.
3. Let |layoutChild| be a new {{LayoutChild}} with internal slot(s):
- {{[[box]]}} set to |box|.
- {{[[styleMap]]}} set to a new {{StylePropertyMapReadOnly}} populated with
only the
computed values for properties listed in
|childInputProperties|.
4.
Set |layoutChildMap|[|workletGlobalScope|] to |layoutChild|.
4. Return the result of
get |layoutChildMap|[|workletGlobalScope|]
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]]}}.
When the user agent wants to
update a layout child style given |box|, it
must
run the following steps:
1. Assert that:
- |box| is currently attached to the
box tree.
2. If |box|'s
containing block is not a
layout API container, abort all these
steps.
3. Let |layoutChildMap| be |box|'s {{[[layoutChildMap]]}}.
4.
For each |layoutChild| in |layoutChildMap|:
1. Let |styleMap| be |layoutChild|'s {{[[styleMap]]}}.
2. Update |styleMap|'s
declarations based on the |box|'s new computed style.
When the computed style of a box changes the user agent must run the update a layout child
style algorithm.
Layout Fragments {#layout-fragments}
------------------------------------
[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):
- \[[layoutFragmentRequest]] a
{{LayoutFragmentRequest}}, this is the fragment request which generated this fragment.
- \[[generator]] the generator which produced this
fragment.
A {{LayoutFragment}} represents a CSS fragment of a {{LayoutChild}} after layout has occurred
on that child. This is produced by the {{LayoutChild/layoutNextFragment()}} method.
The {{LayoutFragment}} has {{LayoutFragment/inlineSize}} and {{LayoutFragment/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 {{LayoutFragment/inlineSize}} and {{LayoutFragment/blockSize}} attributes cannot be changed. If
the current layout requires a different {{LayoutFragment/inlineSize}} or
{{LayoutFragment/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 {{LayoutFragment}} by setting its
{{LayoutFragment/inlineOffset}} and {{LayoutFragment/blockOffset}} attributes. If not set by the
author they default to zero. The {{LayoutFragment/inlineOffset}} and {{LayoutFragment/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.
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 {
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.all.inline;
const minContentSize = childrenSizes.reduce((max, childSizes) => {
return Math.max(max, childSizes.minContentSize);
}, 0) + edges.all.inline;
return {maxContentSize, minContentSize};
}
async layout(children, edges, constraints, styleMap) {
// Determine our (inner) available size.
const availableInlineSize = constraints.fixedInlineSize - edges.all.inline;
const availableBlockSize = constraints.fixedBlockSize ?
constraints.fixedBlockSize - edges.all.block : null;
const childFragments = [];
const childConstraints = { availableInlineSize, availableBlockSize };
const childFragments = await Promise.all(children.map((child) => {
return child.layoutNextFragment(childConstraints);
}));
let blockOffset = edges.all.blockStart;
for (let fragment of childFragments) {
// Position the fragment in a block like manner, centering it in the
// inline direction.
fragment.blockOffset = blockOffset;
fragment.inlineOffset = Math.max(
edges.all.inlineStart,
(availableInlineSize - fragment.inlineSize) / 2);
blockOffset += fragment.blockSize;
}
const autoBlockSize = blockOffset + edges.all.blockEnd;
return {
autoBlockSize,
childFragments,
};
}
});
A layout API container can communicate with other layout API containers by using the
{{LayoutFragment/data}} attribute. This is set by the {{FragmentResultOptions/data}} member in the
{{FragmentResultOptions}} dictionary.
The {{LayoutFragment}}'s {{LayoutFragment/breakToken}} specifies where the {{LayoutChild}} last
fragmented. If the {{LayoutFragment/breakToken}} is null the {{LayoutChild}} wont produce any more
{{LayoutFragment}}s for that token chain. The {{LayoutFragment/breakToken}} can be passed to the
{{LayoutChild/layoutNextFragment()}} function to produce the next {{LayoutFragment}} for a
particular child. The {{LayoutFragment/breakToken}} cannot be changed.
If the current layout requires a different {{LayoutFragment/breakToken}} the author must perform
{{LayoutChild/layoutNextFragment()}} again with different arguments.
When the user agent wants to
create a layout fragment given |generator|,
|layoutFragmentRequest|, and |internalFragment|, it
must run the following steps:
1. Let |targetRealm| be |generator|'s
Realm.
2. Let |fragment| be a new {{LayoutFragment}} with:
- The {{[[layoutFragmentRequest]]}} internal slot being |layoutFragmentRequest|.
- The {{[[generator]]}} internal slot being |generator|.
- {{LayoutFragment/inlineSize}} being |internalFragment|'s
inline size relative to
the
current layout's writing mode.
- {{LayoutFragment/blockSize}} being |internalFragment|'s
block size relative to the
current layout's writing mode.
- {{LayoutFragment/inlineOffset}} initially set to 0.
- {{LayoutFragment/blockOffset}} initially set to 0.
- {{LayoutFragment/breakToken}} being a new {{ChildBreakToken}} representing
|internalFragment|'s internal break token, if any.
- If |internalFragment| has a |clonedData| object stored with it, let
{{LayoutFragment/data}} being the result of
StructuredDeserialize(|clonedData|, |targetRealm|), otherwise null.
3. Return |fragment|.
Intrinsic Sizes {#intrinsic-sizes}
----------------------------------
[Exposed=LayoutWorklet]
interface IntrinsicSizes {
readonly attribute double minContentSize;
readonly attribute double maxContentSize;
};
The {{IntrinsicSizes}} object has internal slot(s):
- \[[intrinsicSizesRequest]] a
{{IntrinsicSizesRequest}}, this is the intrinsic sizes request which generated these
intrinsic sizes.
A {{IntrinsicSizes}} object represents the min-content size and max-content size of a
CSS box. It has {{IntrinsicSizes/minContentSize}} and {{IntrinsicSizes/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 {{IntrinsicSizes/minContentSize}} and {{IntrinsicSizes/maxContentSize}} cannot be changed. They
must not change for a {{LayoutChild}} within the current layout pass.
The example below shows the border-box intrinsic sizes of two children.
<style>
.child-0 {
width: 380px;
border: solid 10px;
}
.child-1 {
border: solid 5px;
}
.box {
display: layout(intrinsic-sizes-example);
font: 25px/1 Ahem;
}
</style>
<div class="box">
<div class="child-0"></div>
<div class="child-1">XXX XXXX</div>
</div>
registerLayout('intrinsic-sizes-example', class {
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() {}
});
When the user agent wants to
create an intrinsic sizes object given
|intrinsicSizesRequest|, and |internalIntrinsicSizes|, it
must run the following steps:
1. Let |intrinsicSizes| be a new {{IntrinsicSizes}} with:
- The {{[[intrinsicSizesRequest]]}} internal slot being |intrinsicSizesRequest|.
- {{IntrinsicSizes/minContentSize}} being |internalIntrinsicSizes|'
border box
min-content contribution, relative to the
current layout's writing mode.
- {{IntrinsicSizes/maxContentSize}} being |internalIntrinsicSizes|'s
border box
max-content contribution, relative to the
current layout's writing mode.
2. Return |intrinsicSizes|.
Layout Constraints {#layout-constraints}
----------------------------------------
[Exposed=LayoutWorklet]
interface LayoutConstraints {
readonly attribute double availableInlineSize;
readonly attribute double availableBlockSize;
readonly attribute double? fixedInlineSize;
readonly attribute double? fixedBlockSize;
readonly attribute double percentageInlineSize;
readonly attribute double percentageBlockSize;
readonly attribute double? blockFragmentationOffset;
readonly attribute BlockFragmentationType blockFragmentationType;
readonly attribute any data;
};
dictionary LayoutConstraintsOptions {
double availableInlineSize = 0;
double availableBlockSize = 0;
double fixedInlineSize;
double fixedBlockSize;
double percentageInlineSize;
double percentageBlockSize;
double blockFragmentationOffset;
BlockFragmentationType blockFragmentationType = "none";
any data;
};
enum BlockFragmentationType { "none", "page", "column", "region" };
A {{LayoutConstraints}} object is passed into the layout method which represents the all the
constraints for the current layout to perform layout inside. It is also used to pass
information about the available space into a child layout.
The {{LayoutConstraints}} object has {{LayoutConstraints/availableInlineSize}} and
{{LayoutConstraints/availableBlockSize}} attributes. This represents the available space for
a {{LayoutFragment}} which the layout should 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 {{LayoutConstraints/fixedInlineSize}} or {{LayoutConstraints/fixedBlockSize}} are specified the
current layout should produce a {{LayoutFragment}} with a the specified size in the
appropriate direction.
The layout algorithm performs a flexbox-like distribution of spare space in the inline direction. It
creates child layout constraints which specify that a child should be a fixed inline size.
registerLayout('flex-distribution-like', class {
async intrinsicSizes(children, edges, styleMap) {
const childrenSizes = children.map((child) => {
return child.intrinsicSizes();
});
const maxContentSize = childrenSizes.reduce((sum, childSizes) => {
return sum + childSizes.maxContentSize;
}, 0) + edges.all.inline;
const minContentSize = childrenSizes.reduce((max, childSizes) => {
return sum + childSizes.minContentSize;
}, 0) + edges.all.inline;
return {maxContentSize, minContentSize};
}
async layout(children, edges, constraints, styleMap) {
// Determine our (inner) available size.
const availableInlineSize =
constraints.fixedInlineSize - edges.all.inline;
const availableBlockSize = constraints.fixedBlockSize ?
constraints.fixedBlockSize - edges.all.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 fragment of childFragments) {
fragment.inlineOffset = inlineOffset;
fragment.blockOffset = edges.all.blockStart;
inlineOffset += fragment.inlineSize;
maxChildBlockSize = Math.max(maxChildBlockSize, fragment.blockSize);
}
return {
autoBlockSize: maxChildBlockSize + edges.all.block,
childFragments,
};
}
});
The {{LayoutConstraints}} object has {{LayoutConstraints/percentageInlineSize}} and
{{LayoutConstraints/percentageBlockSize}} attributes. These represent the size that a layout
percentages should be resolved against while performing layout.
The {{LayoutConstraints}} has a {{LayoutConstraints/blockFragmentationType}} attribute. The
current layout should produce a {{LayoutFragment}} which fragments at the
{{LayoutConstraints/blockFragmentationOffset}} if possible.
The current layout can choose not to fragment a {{LayoutChild}} based on the
{{LayoutConstraints/blockFragmentationType}}, for example if the child has a property like
''break-inside: avoid-page;''.
When the user agent wants to
create a layout constraints object given |sizingMode|, box|,
and |internalLayoutConstraints|, it
must run the following steps:
1. If |sizingMode| is
"block-like"
then:
1. Let |fixedInlineSize| be the result of calculating |box|'s
border-box
inline size (relative to |box|'s writing mode) exactly like block containers do.
2. Let |fixedBlockSize| be null if |box|'s
block size is ''height/auto'', otherwise
the result of calculating |box|'s
border-box block size exactly like block
containers do.
3. Return a new {{LayoutConstraints}} object with:
- {{LayoutConstraints/fixedInlineSize}}, and {{LayoutConstraints/availableInlineSize}}
set to |fixedInlineSize|.
- {{LayoutConstraints/percentageInlineSize}} set to |internalLayoutConstraints|'
percentage resolution size in the inline axis (relative to |box|'s writing mode).
- {{LayoutConstraints/fixedBlockSize}} set to |fixedBlockSize|.
- {{LayoutConstraints/availableBlockSize}} set to |fixedBlockSize| if not null,
otherwise |internalLayoutConstraints|'
available space in the block axis
(relative to |box|'s writing mode).
- {{LayoutConstraints/percentageBlockSize}} set to |internalLayoutConstraints|'
percentage resolution size in the block axis (relative to |box|'s writing mode).
2. If |sizingMode| is
"manual"
then:
1. Return a new {{LayoutConstraints}} object with:
- {{LayoutConstraints/fixedInlineSize}}/{{LayoutConstraints/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 [[#interaction-sizing]] for different scenarios when this can occur.
- {{LayoutConstraints/availableInlineSize}}/{{LayoutConstraints/availableBlockSize}} set
to |internalLayoutConstraints|'
available space.
- {{LayoutConstraints/percentageInlineSize}}/{{LayoutConstraints/percentageBlockSize}}
set to |internalLayoutConstraints|' percentage resolution size.
Breaking and Fragmentation {#breaking-and-fragmentation}
--------------------------------------------------------
[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" };
A {{LayoutChild}} can produce multiple {{LayoutFragment}}s. A {{LayoutChild}} may fragment in the
block direction if a {{LayoutConstraints/blockFragmentationType}} is not none. Additionally
{{LayoutChild}} which represents inline-level content, may fragment line by line if the
layout options' {{LayoutOptions/childDisplay}} (set by
layoutOptions
) is "normal"
.
A subsequent {{LayoutFragment}} is produced by using the previous {{LayoutFragment}}'s
{{LayoutFragment/breakToken}}. This tells the child layout to produce a {{LayoutFragment}}
starting at the point encoded in the {{ChildBreakToken}}.
This example shows a simple layout which indents child fragments for a certain number of
lines.
This example also demonstrates using the previous {{LayoutFragment/breakToken}} of a
{{LayoutFragment}} to produce the next fragment for the {{LayoutChild}}.
It also demonstrates using the {{BreakToken}} to respect the {{LayoutConstraints}}'
{{LayoutConstraints/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('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.all.inline;
const availableBlockSize = constraints.fixedBlockSize ?
constraints.fixedBlockSize - edges.all.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.all.blockStart;
let child = children.shift();
while (child) {
const shouldIndent = lines-- > 0;
// Adjust the inline size for the indent.
const childAvailableInlineSize = shouldIndent ?
availableInlineSize - indent : availableInlineSize;
const 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.all.inlineStart + indent : edges.all.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.all.blockEnd;
// Return our fragment.
const result = {
autoBlockSize,
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 the 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 {
async layout(children, edges, constraints, styleMap, breakToken) {
edges.padding.inlineStart; // 5 (as 10% * 50px = 5px).
edges.border.blockEnd; // 2
edges.scrollbar.inlineEnd; // UA-dependent, may be 0 or >0 (e.g. 16).
edges.all.block; // 14 (2 + 5 + 5 + 2).
}
}
Interactions with other Modules {#interactions-with-other-modules}
==================================================================
This section describes how other CSS modules interact with the CSS Layout API.
Sizing {#interaction-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
{{LayoutConstraints/fixedInlineSize}} and {{LayoutConstraints/fixedBlockSize}} attributes to do so.
The layout API container can be passed size information in different ways depending on the
value of layout options' {{LayoutOptions/sizing}} (set by
layoutOptions
on the class).
If the value of layout options' {{LayoutOptions/sizing}} is
"block-like"
, then the {{LayoutConstraints}} passed to the layout API container:
- Must calculate and set {{LayoutConstraints/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 ''width/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 {{LayoutConstraints/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 ''height/auto'' block size, and cannot be determined
ahead of time, {{LayoutConstraints/fixedBlockSize}} must be set to null
.
If the value of layout options' {{LayoutOptions/sizing}} is
"manual"
, then the user-agent must not pre-calculate
{{LayoutConstraints/fixedInlineSize}} and/or {{LayoutConstraints/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 ''width/auto'' inline size, the user agent must set the
{{LayoutConstraints/fixedInlineSize}} to the stretch-fit inline size.
Note: In the example below the
layout API container has its inline size set to 50.
<style>
#container {
width: 100px;
height: 100px;
box-sizing: border-box;
padding: 5px;
}
#layout-api {
display: layout(foo);
margin: 0 20px;
}
</style>
<div id="container">
<div id="layout-api"></div>
</div>
### Positioned layout sizing ### {#interaction-sizing-positiong-layout}
If a layout API container is out-of-flow positioned the user agent must solve the
positioned size equations ([[css-position-3#abs-non-replaced-width]],
[[css-position-3#abs-non-replaced-height]]), and set the appropriate
{{LayoutConstraints/fixedInlineSize}} and {{LayoutConstraints/fixedBlockSize}}.
Note: In the example below the
layout API container has its inline and block size fixed
to 80.
<style>
#container {
position: relative;
width: 100px;
height: 100px;
}
#layout-api {
display: layout(foo);
top: 10px;
bottom: 10px;
left: 10px;
right: 10px;
position: absolute;
}
</style>
<div id="container">
<div id="layout-api"></div>
</div>
Positioning {#interaction-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 {{LayoutFragment/inlineOffset}} and {{LayoutFragment/blockOffset}} 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.
Note: In the example below:
- "child-relative" would be the only child passed to the author's layout. If it was positioned
at ({{LayoutFragment/inlineOffset}}
= 20
, {{LayoutFragment/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: 100px;
height: 100px;
}
#child-relative {
position: relative;
left: 5px;
top: 10px;
}
</style>
<div id="container">
<div id="child-relative"></div>
<div id="child-absolute"></div>
</div>
Overflow {#interaction-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.
Fragmentation {#interaction-fragmentation}
------------------------------------------
A parent layout can ask the current layout to fragment by setting the
{{LayoutConstraints/blockFragmentationType}} and {{LayoutConstraints/blockFragmentationOffset}}.
E.g. [[css-multicol-1]] layout would set a {{LayoutConstraints/blockFragmentationType}} to
"column"
and set the {{LayoutConstraints/blockFragmentationOffset}} to where it needs the
child to fragment.
Alignment {#interaction-alignment}
----------------------------------
The first/last baseline sets of a layout API container is generated exactly like block
containers do (see [[css-align-3#baseline-export]]). Except that the order of the in-flow children
should be determined by the in which they are returned form the layout method (via
{{FragmentResultOptions/childFragments}}) instead of the document order.
Note: In a future level of the specification there will be the ability for the author to define the
baselines themselves. This will be of the form:
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 baselineRequest of constraints.baselineRequests) {
// baselineRequest === 'alphabetic', or something else.
result.baselines[baselineRequest] = 25;
}
return result;
}
});
Layout {#layout}
================
This section describes how the CSS Layout API interacts with the user agent's layout engine.
Layout Invalidation {#layout-invalidation}
------------------------------------------
Each box has an associated layout valid flag. It may be either
layout-valid or layout-invalid. It is initially set to layout-invalid.
Each box has an associated intrinsic sizes valid flag. If may be either
intrinsic-sizes-valid or intrinsic-sizes-invalid. It is initially set to
intrinsic-sizes-invalid.
Invalidate layout functions must be run when the user agent recalculates the computed
style for a box, or when the children's computed style of that box is recalculated.
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 descendant change),
and this invalidation is to be propagated up the box tree, set the layout valid flag
on the current box to layout-invalid and set the intrinsic sizes valid flag on
the current box to intrinsic-sizes-invalid.
When the computed style of a layout API container changes, and this change effects
the values inside the {{LayoutEdges}} object, set the layout valid flag of the box to
layout-invalid, and set the intrinsic sizes valid flag of the box to
intrinsic-sizes-invalid.
If the computed style changes effects the values inside the {{LayoutConstraints}} object, just set
the intrinsic sizes valid flag of the box to intrinsic-sizes-invalid.
Note: As the {{LayoutConstraints}} object is only passed into the layout function there is no need
to invalidate the intrinsic sizes.
Note: As an example the following properties could change the {{LayoutEdges}} object:
- 'padding-top'
- 'border-left-width'
- 'overflow-y'
And the following properties could change the {{LayoutConstraints}} object:
- 'width'
- 'max-width'
- 'height'
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.
Layout Engine {#layout-engine}
------------------------------
Issue: Currently the API is in an iterable generator form. Based on implementation experience, and
web developer experience, this may change to promise based API instead. There are both pros and
cons to each of these.
Promises
- Better error reporting.
- Potentially better developer ergonomics.
Generator
- More "strict" - can only perform layout operations. Don't have to restrict which promise APIs
work each call.
- Potentially better bindings overhead.
### Request Objects ### {#request-objects}
[Exposed=LayoutWorklet]
interface IntrinsicSizesRequest {
};
[Exposed=LayoutWorklet]
interface LayoutFragmentRequest {
};
typedef (IntrinsicSizesRequest or LayoutFragmentRequest)
LayoutFragmentRequestOrIntrinsicSizesRequest;
The {{IntrinsicSizesRequest}} has internal slot(s):
- \[[layoutChild]] a {{LayoutChild}}, this is the
child which the intrinsic sizes must be calculated for.
The {{LayoutFragmentRequest}} has internal slot(s):
- \[[layoutChild]] a {{LayoutChild}}, this is the
child which the fragment must be generated for.
- \[[layoutConstraints]] a
{{LayoutConstraintsOptions}} dictionary, these are the input constraints to the
{{LayoutChild}}'s layout algorithm.
- \[[breakToken]] a {{ChildBreakToken}} object,
which is the break token the layout must be resumed with.
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 {{LayoutFragment}} to return to the author's code.
Instead it returns a {{LayoutFragmentRequest}}. This is a completely opaque object to the author but
contains internal slots which encapsulates the {{LayoutChild/layoutNextFragment()}} method call.
When a {{LayoutFragmentRequest}}(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 {{LayoutFragment}}(s) have been produced by the engine, the user-agent will "tick"
the generator object with the resulting {{LayoutFragment}}(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 LayoutConstraints object, and a
// BreakToken to (if paginating for printing for example) and generates a
// LayoutFragment.
layoutEntry(rootBox, rootPageConstraints, breakToken) {
return layoutFragment({
box: rootBox,
layoutConstraints: rootPageConstraints,
breakToken: breakToken,
});
}
// This function takes a LayoutFragmentRequest and calls the appropriate
// layout algorithm to generate the a LayoutFragment.
layoutFragment(fragmentRequest) {
const box = fragmentRequest.layoutChild;
const algorithm = selectLayoutAlgorithmForBox(box);
const fragmentRequestGenerator = algorithm.layout(
fragmentRequest.layoutConstraints,
box.children,
box.styleMap,
fragmentRequest.breakToken);
let nextFragmentRequest = fragmentRequestGenerator.next();
while (!nextFragmentRequest.done) {
// 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 LayoutFragment.
}
}
Performing Layout {#performing-layout}
--------------------------------------
// This is the final return value from the author defined layout() method.
dictionary FragmentResultOptions {
double inlineSize = 0;
double blockSize = 0;
double autoBlockSize = 0;
sequence<LayoutFragment> childFragments = [];
any data = null;
BreakTokenOptions breakToken = null;
};
dictionary IntrinsicSizesResultOptions {
double maxContentSize;
double minContentSize;
};
### 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 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.
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 |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 |children| be a new
list.
5.
For each |childBox| in |childBoxes| perform the following substeps:
1. Let |layoutChild| be the result of
get a layout child given |workletGlobalScope|,
|name|, and |childBox|.
2.
Append |layoutChild| to |children|.
6. Let |edges| be a new {{LayoutEdgeSizes}} populated with the
computed value for all the
box model edges for |box|.
7. Let |styleMap| be a new {{StylePropertyMapReadOnly}} populated with
only the
computed values for properties listed in |inputProperties| for |box|.
Issue: We may want to store |styleMap| on |box| instead, similar to |layoutInstance|.
8. 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.
9. Let |intrinsicSizesFunction| be |definition|'s
intrinsic sizes function.
10. Let |intrinsicSizesGenerator| be the result of
Invoke(|intrinsicSizesFunction|, |layoutInstance|, «|children|, |edges|,
|styleMap|»).
If an exception is
thrown the let |box| fallback to the
flow layout and abort
all these steps.
11. 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.
12. 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.
13. 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|, |internalLayoutConstraints|, 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 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.
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|,
|internalLayoutConstraints|, |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|,
|internalLayoutConstraints|, |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 |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 |sizingMode| be |definition|'s
layout options'
{{LayoutOptions/sizing}} property.
4. Let |inputProperties| be |definition|'s
input properties.
5. Let |children| be a new
list.
6.
For each |childBox| in |childBoxes| perform the following substeps:
1. Let |layoutChild| be the result of
get a layout child given |workletGlobalScope|,
|name|, and |childBox|.
2.
Append |layoutChild| to |children|.
7. Let |edges| be a new {{LayoutEdgeSizes}} populated with the
computed value for all the
box model edges for |box|.
8. Let |layoutConstraints| be the result of
create a layout constraints object given
|internalLayoutConstraints|, |box|, and |sizingMode|.
9. Let |styleMap| be a new {{StylePropertyMapReadOnly}} populated with
only the
computed values for properties listed in |inputProperties| for |box|.
Issue: We may want to store |styleMap| on |box| instead, similar to |layoutInstance|.
10. Let |breakToken| be a new {{BreakToken}} populated with the appropriate information from
|internalBreakToken|.
If |internalBreakToken| is null, let |breakToken| be null.
11. 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.
12. Let |layoutFunction| be |definition|'s
layout function.
13. Let |layoutGenerator| be the result of
Invoke(|layoutFunction|, |layoutInstance|,
«|children|, |edges|, |layoutConstraints|, |styleMap|, |breakToken|»).
If an exception is
thrown the let |box| fallback to the
flow layout and abort
all these steps.
14. 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.
15. 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.
16.
For each |childFragment| in |fragment|'s
{{FragmentResultOptions/childFragments}}, perform the following stubsteps:
1. If |childFragment|'s {{[[generator]]}} internal slot is not equal to |layoutGenerator|,
then let |box| fallback to the
flow layout and abort all these steps.
17. If |sizingMode| is
"block-like"
:
- Then:
1. Let |inlineSize| be |layoutConstraints|' {{LayoutConstraints/fixedInlineSize}}. (This
value must be set if we are using
"block-like"
sizing).
2. 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 {{FragmentResultOptions/autoBlockSize}} as the "intrinsic
height".
- Otherwise (|sizingMode| is
"manual"
):
1. Let |inlineSize| be |fragment|'s {{FragmentResultOptions/inlineSize}}.
2. Let |blockSize| be |fragment|'s {{FragmentResultOptions/blockSize}}.
18. Return a
fragment for |box| with:
- The
inline size set to |inlineSize|.
- The
block size set to |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
{{LayoutFragment/inlineOffset}} and {{LayoutFragment/blockOffset}}.
- The
fragmentation break information set to |fragment|'s
{{FragmentResultOptions/breakToken}}.
- Let |clonedData| be the result of invoking
StructuredSerializeForStorage on
|fragment|'s {{FragmentResultOptions/data}}.
The user agent must store |clonedData| with the
fragment.
### 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 |box|, |definition|, and
|workletGlobalScope|, it
must run the following steps:
1. Let |layoutClassInstanceMap| be |box|'s
layout class instances map.
2. Let |layoutInstance| be the result of
get
|layoutClassInstanceMap|[|workletGlobalScope|]. If |layoutInstance| is null, run the
following steps:
1. If the
constructor valid flag on |definition| is false, then return failure and
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|[|workletGlobalScope|] 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 |done| be a
boolean initialized to
false
.
2. Let |nextValue| be undefined.
3. Perform the following substeps until |done| is
true
:
1. Let |nextFunction| be the result of
Get(|generator|,
"next"
).
2. If the result of
IsCallable(|nextFunction|) is false,
throw a
TypeError and abort all these steps.
3. Let |nextResult| be the result of calling
Invoke(|nextFunction|, |generator|,
«|nextValue|»).
If an exception is
thrown return failure, and abort all these steps.
4. If the result of
Type(|nextResult|) is not Object,
throw a
TypeError
and abort all these steps.
5. Let |requestOrRequests| be the result of
Get(nextResult|,
"value"
).
6. Let |done| be the result of
Get(|nextResult|,
"done"
).
7. If the result of
GetMethod(|requestOrRequests|,
@@iterable
) exists
then:
1. Set |results| to be a new
empty list.
2. Let |requests| be the result of
converting |requestOrRequests| to a
sequence<LayoutFragmentRequestOrIntrinsicSizesRequest>
.
If an exception is
thrown, rethrow the exception and abort all these steps.
3.
For each |request| in |requests| perform the following substeps:
1. Let |result| be the result of
produce a generator result given |request|,
|generator|, |generatorType|.
2.
Append |result| to |results|.
4. Set |nextValue| to be |results|.
5. Continue.
8. Let |request| be the result of
converting |requestOrRequests| to a
{{LayoutFragmentRequestOrIntrinsicSizesRequest}}.
If an exception is
thrown, rethrow the exception and abort all these steps.
9. Let |result| be the result of
produce a generator result given |request|,
|generator|, |generatorType|.
If
produce a generator result returns failure, return failure, and abort all
these steps.
10. Set |nextValue| to be |result|.
11. Continue.
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.
3. Return the result of calling
Get(|nextResult|,
"value"
).
When the user agent wants to
produce a generator result given |request|, |generator|, and
|generatorType|, it
must run the following steps:
1. If |request| is a {{IntrinsicSizesRequest}} then:
1. Let |layoutChild| be the result of looking up the internal slot
{{IntrinsicSizesRequest/[[layoutChild]]}} on |request|.
2. Let |box| be the result of looking up the internal slot {{LayoutChild/[[box]]}} on
|layoutChild|.
3. If |box| is
not attached to the
box tree, return failure and abort all
these steps.
Note: The author may hold onto a {{LayoutChild}} from a previous invocation. It is
assumed that a box is only ever attached to the box-tree once, and not re-used.
4. Let |internalIntrinsicSizes| be the result of the user agent calculating the
border
box min/max content contribution of |box|.
5. Return the result of
create an intrinsic sizes object given |request|, and
|internalIntrinsicSizes|.
2. If |request| is a {{LayoutFragmentRequest}} and |generatorType| is
"layout"
then:
1. Let |layoutChild| be result of looking up the internal slot
{{LayoutFragmentRequest/[[layoutChild]]}} on |request|.
2. Let |box| be the result of looking up the internal slot {{LayoutChild/[[box]]}} on
|layoutChild|.
3. If |box| is
not attached to the
box tree, return failure and abort all
these steps.
Note: The author may hold onto a {{LayoutChild}} from a previous invocation. It is
assumed that a box is only ever attached to the box-tree once, and not re-used.
4. Let |childLayoutConstraints| be the result of looking up the internal slot
{{LayoutFragmentRequest/[[layoutConstraints]]}} on |request|.
5. Let |childBreakToken| be the result of looking up the internal slot
{{LayoutFragmentRequest/[[breakToken]]}} on |request|.
6. Let |internalFragment| be the result of the user agent producing a
fragment based
on |box|, |childLayoutConstraints|, and |childBreakToken|.
7. Return the result of
create a layout fragment given |generator|, |request|, and
|internalFragment|.
3. Return failure (neither of the above branches was taken).
Security Considerations {#security-considerations}
==================================================
There are no known security issues introduced by these features.
Privacy Considerations {#privacy-considerations}
================================================
There are no known privacy issues introduced by these features.