1. 소개
이 섹션은 규범적이지 않습니다.
CSS의 레이아웃 단계는 프래그먼트를 박스 트리로부터 생성하고 위치를 지정하는 역할을 합니다.
이 명세서는 개발자가 계산된 스타일 및 박스 트리 변경에 따라 박스의 레이아웃을 할 수 있도록 하는 API를 설명합니다.
2. 레이아웃 API 컨테이너
대체 값이 <display-inside> 생성식에 추가됩니다:
layout(<ident>)
.
- layout()
- 이 값은 요소가 레이아웃 API 컨테이너 박스를 생성하도록 합니다.
레이아웃 API 컨테이너는 <display-inside> 계산된 값이 layout()인 요소가 생성하는 박스입니다.
레이아웃 API 컨테이너는 자신의 내용에 대해 새로운 레이아웃 API 서식 컨텍스트를 설정합니다. 이는 블록 서식 컨텍스트를 설정하는 것과 같지만, 블록 레이아웃 대신 저자가 제공한 레이아웃이 사용됩니다. 예를 들어, 플로트는 레이아웃 API 컨테이너에 침범하지 않으며, 컨테이너의 마진은 내용물의 마진과 병합되지 않습니다.
레이아웃 API 컨테이너의 모든 인플로우 자식은 레이아웃 API 자식이라 하며, 저자가 정의한 레이아웃을 사용하여 배치됩니다.
레이아웃 API 컨테이너는 자신의 내용에 대해 블록 컨테이너와 동일하게 포함 블록을 형성합니다. [CSS21]
참고: 향후 명세 레벨에서는 포함 블록 동작을 오버라이드할 수 있는 방법이 추가될 수 있습니다.
overflow 속성은 레이아웃 API 컨테이너에 적용됩니다. 이는 §4.3 Overflow에서 다룹니다.
레이아웃이 전체적으로 저자에게 달려 있으므로, 다른 레이아웃 모드(예: flex, block)에서 사용되는 속성들이 반드시 적용되진 않을 수 있습니다. 예를 들어 저자가 자식의 margin 속성을 존중하지 않을 수도 있습니다.
2.1. 레이아웃 API 컨테이너 페인팅
레이아웃 API 컨테이너의 자식들은
인라인 블록과 정확히 동일하게 페인트됩니다 [CSS21], 단
레이아웃 메서드에서 반환되는 순서(childFragments
)가
문서의 원래 순서 대신 사용되고, z-index 값이 auto가 아닌 경우에는 position이 static이어도 스태킹 컨텍스트가 생성됩니다.
2.2. 박스 트리 변환
레이아웃 API 자식은 layout
options' childDisplay
값(클래스의 layoutOptions
로 설정)에 따라 다르게 동작할 수 있습니다.
layout options' childDisplay
값이 "block"
이면 해당 자식의 display 값이 blockified됩니다. 이는 flex 컨테이너나 grid
컨테이너의 자식들과 유사합니다.
[css3-display]를 참고하세요.
layout options' childDisplay
값이 "normal"
이면 blockification이 발생하지 않습니다. 대신
<display-outside> 계산된 값이 inline인 자식(root inline box)은
LayoutFragment
를
각 줄마다 하나씩 생성하며, layoutNextFragment()
가
호출될 때마다 반환합니다.
참고: 저자는 각 줄의 사용 가능한 인라인 크기를 조정하고, 각 줄을 개별적으로 위치시킬 수 있습니다.
LayoutChild
가 root inline box를 나타내는 경우에도 추가적인 변환이 적용됩니다.
-
인라인 레벨 박스 내부의 블록 레벨 박스는 인라이니파이(inlinified) 됩니다. 즉, <display-outside>가 inline으로 설정됩니다.
-
인라인 레벨 박스 내부의 float는 플로우에서 제외되지 않습니다. 대신 인플로우로 처리되어 인라이니파이(inlinified)되어야 합니다.
위 두 경우 모두 자식들은 원자 인라인(atomic inlines)이 됩니다.
참고: 사용자 에이전트는 블록 레벨 박스를 만났을 때 어떤 "인라인 분할"이나 프래그먼트화를 수행하지 않습니다.
LayoutChild
로 표현되며,
"block"과 "float" 모두 원자 인라인이 됩니다.
<span id="inline-span"> Text <div id="block"></div> <div id="float"></div> Text </span>
3. 레이아웃 API 모델 및 용어
이 섹션은 저자에게 제공되는 레이아웃 API 개요를 설명합니다.
현재 레이아웃은 지금 레이아웃을 수행 중인 박스에 대한 레이아웃 알고리즘입니다.
부모 레이아웃은 박스의 직접적인 부모에 대한 레이아웃 알고리즘(현재 레이아웃을 요청하는 알고리즘)입니다.
자식 레이아웃은 LayoutChild
의
현재 레이아웃에 대한 레이아웃 알고리즘입니다.
3.1. 레이아웃 자식
[Exposed=LayoutWorklet] interfaceLayoutChild
{ readonly attribute StylePropertyMapReadOnly styleMap; IntrinsicSizesRequest intrinsicSizes(); LayoutFragmentRequest layoutNextFragment(LayoutConstraintsconstraints
, ChildBreakTokenbreakToken
); };
LayoutChild
는
내부 슬롯을 가집니다:
-
[[box]]
CSS 박스 -
[[styleMap]]
StylePropertyMapReadOnly
, 자식의 계산된 스타일이며,childInputProperties
에 나열된 속성만 포함됩니다.
LayoutChild
는
레이아웃이 발생하기 전 CSS 박스를
나타냅니다. (박스들은 모두 display의 계산된 값이 none이 아닙니다.)
LayoutChild
는
자체적으로 레이아웃 정보(인라인 또는 블록 크기 등)를 포함하지 않지만,
레이아웃 정보를 포함하는 LayoutFragment
를
생성할 수 있습니다.
저자는 이 API로 LayoutChild
를 직접 생성할
수 없으며,
이는 렌더링 엔진의 별도 단계(스타일 해석 이후)에서 생성됩니다.
LayoutChild
는
다음에 의해 생성될 수 있습니다:
-
참고: ::first-letter 또는 ::first-line 등 다른 의사 요소는 레이아웃 목적으로
LayoutChild
를 생성하지 않습니다. 이들은 텍스트 노드에 대한 추가 스타일 정보입니다. -
익명 박스(anonymous box). 예를 들어 익명 박스는 다음의 결과로 삽입될 수 있습니다:
-
blockification된 텍스트 노드(또는 좀 더 일반적으로 root inline box가 blockification된 경우)
-
display: table-cell인 요소가 display: table인 부모가 없는 경우
-
LayoutChild
로 배치됩니다:
<style> #box::before { content: 'hello!'; } </style> <div id="box">A block level box with text.</div> <img src="..." />
LayoutChild
로 배치되며,
root inline box를 공유합니다:
This is a next node, <span>with some additional styling, that may</span> break over<br>multiple lines.
여러 개의 원자 인라인이 아닌 경우, 동일한 LayoutChild
내에
배치되어
렌더링 엔진이 요소 경계를 넘어 텍스트 셰이핑을 수행할 수 있도록 합니다.
LayoutFragment
를
생성해야 하지만,
세 개의 원자 인라인이 아닌 경우입니다:
ع<span style="color: blue">ع</span>ع
현재 박스를 레이아웃하는 레이아웃 메서드에 LayoutChild
배열이
자식으로 전달됩니다.
styleMap
을 LayoutChild
this에서 가져올 때,
사용자 에이전트는 다음 단계를 수행해야 합니다:
-
this의
StylePropertyMapReadOnly
를[[styleMap]]
내부 슬롯에 담아 반환합니다.
layoutNextFragment(constraints, breakToken)
메서드를 LayoutChild
this에서 호출할 때,
사용자 에이전트는 다음 단계를 수행해야 합니다:
-
request를 새
LayoutFragmentRequest
로 생성하고 내부 슬롯을 설정합니다:-
[[layoutChild]]
를 this로 설정 -
[[layoutConstraints]]
를 constraints로 설정 -
[[breakToken]]
를 breakToken로 설정
-
-
request를 반환합니다.
intrinsicSizes()
메서드를 LayoutChild
this에서 호출할 때,
사용자 에이전트는 다음 단계를 수행해야 합니다:
-
request를 새
IntrinsicSizesRequest
로 생성하고 내부 슬롯을 설정합니다:-
[[layoutChild]]
를 this로 설정
-
-
request를 반환합니다.
참고: layoutNextFragment()
와 intrinsicSizes()
는
동기적으로 실행되지 않습니다. 전체 설명은 §5.5.1 Request Objects를 참고하세요.
3.1.1. LayoutChildren와 박스 트리
각 box는 [[layoutChildMap]]
내부 슬롯을 가지며, 이는 map으로 LayoutWorkletGlobalScope
와
LayoutChild
ren을
연결합니다.
-
다음을 단언합니다:
-
box는 현재 박스 트리에 연결되어 있다.
-
box의 포함 블록은 레이아웃 API 컨테이너이다.
-
-
layoutChildMap을 box의
[[layoutChildMap]]
으로 설정합니다. -
만약 layoutChildMap[workletGlobalScope]이 존재하지 않는다면, 다음 단계를 실행합니다:
-
definition을 레이아웃 정의 가져오기의 결과로 name, workletGlobalScope를 인자로 얻습니다.
레이아웃 정의 가져오기가 성공했으며, definition이
"invalid"
가 아님을 단언합니다. -
childInputProperties를 definition의 child input properties로 설정합니다.
-
layoutChild를 새
LayoutChild
로 생성하며, 내부 슬롯:-
[[box]]
를 box로 설정 -
[[styleMap]]
을 새StylePropertyMapReadOnly
로 설정하며, childInputProperties에 나열된 속성들에 대한 계산된 값만 포함합니다.
-
-
설정 layoutChildMap[workletGlobalScope] = layoutChild
-
-
가져오기 layoutChildMap[workletGlobalScope]의 결과를 반환합니다.
box가 박스 트리에 삽입될
때, 사용자 에이전트는 모든 LayoutWorkletGlobalScope
에
대해 [[layoutChildMap]]
을
미리 채울 수 있습니다.
box가 박스 트리에서 제거될
때, 사용자 에이전트는 [[layoutChildMap]]
을
반드시 초기화합니다.
-
다음을 단언합니다:
-
box는 현재 박스 트리에 연결되어 있다.
-
-
만약 box의 포함 블록이 레이아웃 API 컨테이너가 아니라면, 모든 단계를 중단합니다.
-
layoutChildMap을 box의
[[layoutChildMap]]
으로 설정합니다. -
각 layoutChild에 대해 layoutChildMap에서:
-
styleMap을 layoutChild의
[[styleMap]]
으로 설정합니다. -
box의 새 계산된 스타일에 따라 styleMap의 declarations를 업데이트합니다.
-
box의 계산된 스타일이 변경되면 사용자 에이전트는 레이아웃 자식 스타일 업데이트 알고리즘을 실행해야 합니다.
3.2. 레이아웃 프래그먼트
[Exposed=LayoutWorklet] interfaceLayoutFragment
{ readonly attribute doubleinlineSize
; readonly attribute doubleblockSize
; attribute doubleinlineOffset
; attribute doubleblockOffset
; readonly attribute anydata
; readonly attribute ChildBreakToken?breakToken
; };
LayoutFragment
는
내부 슬롯을 가집니다:
-
[[layoutFragmentRequest]]
LayoutFragmentRequest
로, 이 프래그먼트를 생성한 프래그먼트 요청입니다. -
[[generator]]
이 프래그먼트를 생성한 제너레이터입니다.
LayoutFragment
는
LayoutChild
에
대한 레이아웃이 완료된 후의 CSS 프래그먼트를 나타냅니다. 이는 layoutNextFragment()
메서드에서 생성됩니다.
LayoutFragment
는
inlineSize
와
blockSize
속성을 가지며, 이는 각각 자식의 레이아웃 알고리즘에서 설정됩니다. 이 값들은 CSS 프래그먼트의 border box 크기를 나타내며,
현재 레이아웃의 작성 모드에 상대적입니다.
inlineSize
와
blockSize
속성은 변경할 수 없습니다.
현재 레이아웃에서 다른 inlineSize
또는 blockSize
가
필요하다면, 저자는 다른 인자로 layoutNextFragment()
를
다시 수행해야 합니다.
현재 레이아웃 내부의 저자는 생성된 LayoutFragment
의
inlineOffset
과
blockOffset
속성을 설정하여 위치를 지정할 수 있습니다. 설정하지 않으면 기본값은 0입니다. inlineOffset
과
blockOffset
은
LayoutFragment
가
부모의 border box에 상대적인 위치를 나타내며, 변환이나 포지셔닝이 적용되기 전입니다(예: 프래그먼트가 상대 위치 지정된
경우).
registerLayout('block-like', class { *intrinsicSizes(children, edges, styleMap) { const childrenSizes = yield children.map((child) => { return child.intrinsicSizes(); }); const maxContentSize = childrenSizes.reduce((max, childSizes) => { return Math.max(max, childSizes.maxContentSize); }, 0) + edges.all.inline; const minContentSize = childrenSizes.reduce((max, childSizes) => { return Math.max(max, childSizes.minContentSize); }, 0) + edges.all.inline; return {maxContentSize, minContentSize}; } *layout(children, edges, constraints, styleMap) { const availableInlineSize = constraints.fixedInlineSize - edges.all.inline; const availableBlockSize = (constraints.fixedBlockSize || Infinity) - edges.all.block; const childFragments = []; const childConstraints = { availableInlineSize, availableBlockSize }; const childFragments = yield children.map((child) => { return child.layoutNextFragment(childConstraints); }); let blockOffset = edges.all.blockStart; for (let fragment of childFragments) { // 프래그먼트를 블록 방식으로 배치하고, 인라인 방향으로 중앙에 위치시킨다. fragment.blockOffset = blockOffset; fragment.inlineOffset = Math.max( edges.all.inlineStart, (availableInlineSize - fragment.inlineSize) / 2); blockOffset += fragment.blockSize; } const autoBlockSize = blockOffset + edges.all.blockEnd; return { autoBlockSize, childFragments, }; } });
레이아웃 API
컨테이너는 data
속성을 사용하여 다른 레이아웃 API
컨테이너들과 통신할 수 있습니다. 이 값은 data
멤버와 FragmentResultOptions
딕셔너리에서 설정됩니다.
LayoutFragment
의
breakToken
은
LayoutChild
가
마지막으로 프래그먼트된 위치를 지정합니다. breakToken
이
null이면 LayoutChild
는 해당 토큰
체인에 대해 더 이상 LayoutFragment
를
생성하지 않습니다. breakToken
은
layoutNextFragment()
함수에 전달되어 특정 자식의 다음 LayoutFragment
를
생성합니다. breakToken
은
변경할 수 없습니다.
현재 레이아웃에서 다른 breakToken
이
필요하다면, 저자는 다른 인자를 사용해 layoutNextFragment()
를
다시 실행해야 합니다.
-
targetRealm을 generator의 Realm으로 설정합니다.
-
fragment를 새
LayoutFragment
로 생성하며:-
[[layoutFragmentRequest]]
내부 슬롯을 layoutFragmentRequest로 설정합니다. -
[[generator]]
내부 슬롯을 generator로 설정합니다. -
inlineSize
를 internalFragment의 인라인 크기로, 현재 레이아웃의 작성 모드에 상대적으로 설정합니다. -
blockSize
를 internalFragment의 블록 크기로, 현재 레이아웃의 작성 모드에 상대적으로 설정합니다. -
inlineOffset
은 처음에 0으로 설정합니다. -
blockOffset
은 처음에 0으로 설정합니다. -
breakToken
을 internalFragment의 내부 break token을 나타내는 새ChildBreakToken
으로 설정합니다(존재하는 경우). -
만약 internalFragment에 clonedData 객체가 저장되어 있다면,
data
를 StructuredDeserialize(clonedData, targetRealm)의 결과로 설정하고, 그렇지 않으면 null로 설정합니다.
-
-
fragment를 반환합니다.
3.3. 내재적 크기
[Exposed=LayoutWorklet] interfaceIntrinsicSizes
{ readonly attribute doubleminContentSize
; readonly attribute doublemaxContentSize
; };
IntrinsicSizes
객체는 내부 슬롯을 가집니다:
-
[[intrinsicSizesRequest]]
IntrinsicSizesRequest
로, 이 내재적 크기를 생성한 intrinsic sizes 요청입니다.
IntrinsicSizes
객체는 CSS box의 min-content
크기와 max-content 크기를 나타냅니다. minContentSize
와
maxContentSize
속성은 현재 레이아웃의 LayoutChild
의
border box min/max-content 기여도를 나타냅니다. 속성 값은 현재 레이아웃의 인라인 방향에 상대적입니다.
minContentSize
와
maxContentSize
는
변경할 수 없습니다. 이는 LayoutChild
가 현재
레이아웃 패스 내에서 반드시 변하지 않아야 합니다.
<style> .child-0 { width: 380px; border: solid 10px; } .child-1 { border: solid 5px; } .box { display: layout(intrinsic-sizes-example); font: 25px/1 Ahem; } </style> <div class="box"> <div class="child-0"></div> <div class="child-1">XXX XXXX</div> </div>
registerLayout('intrinsic-sizes-example', class { *intrinsicSizes(children, edges, styleMap) { const childrenSizes = yield children.map((child) => { return child.intrinsicSizes(); }); childrenSizes[0].minContentSize; // 400, (380+10+10) 고정 크기 자식 childrenSizes[0].maxContentSize; // 400, (380+10+10) 고정 크기 자식 childrenSizes[1].minContentSize; // 100, "XXXX"의 크기 childrenSizes[1].maxContentSize; // 200, "XXX XXXX"의 크기 } *layout() {} });
-
intrinsicSizes를 새
IntrinsicSizes
로 생성하며:-
[[intrinsicSizesRequest]]
내부 슬롯을 intrinsicSizesRequest로 설정합니다. -
minContentSize
를 internalIntrinsicSizes의 border box min-content 기여도로, 현재 레이아웃의 작성 모드에 상대적으로 설정합니다. -
maxContentSize
를 internalIntrinsicSizes의 border box max-content 기여도로, 현재 레이아웃의 작성 모드에 상대적으로 설정합니다.
-
-
intrinsicSizes를 반환합니다.
3.4. 레이아웃 제약 조건
[Constructor
(optional LayoutConstraintsOptionsoptions
),Exposed=LayoutWorklet] interfaceLayoutConstraints
{ readonly attribute doubleavailableInlineSize
; readonly attribute doubleavailableBlockSize
; readonly attribute double?fixedInlineSize
; readonly attribute double?fixedBlockSize
; readonly attribute doublepercentageInlineSize
; readonly attribute doublepercentageBlockSize
; readonly attribute double?blockFragmentationOffset
; readonly attribute BlockFragmentationTypeblockFragmentationType
; readonly attribute anydata
; }; dictionaryLayoutConstraintsOptions
{ doubleavailableInlineSize
= 0; doubleavailableBlockSize
= 0; doublefixedInlineSize
; doublefixedBlockSize
; doublepercentageInlineSize
; doublepercentageBlockSize
; doubleblockFragmentationOffset
; BlockFragmentationTypeblockFragmentationType
= "none"; anydata
; }; enumBlockFragmentationType
{"none"
,"page"
,"column"
,"region"
};
LayoutConstraints
객체는 레이아웃 메서드에 전달되며, 현재 레이아웃이 내부에서
레이아웃을 수행하기 위한 모든 제약 조건을 나타냅니다. 또한 자식
레이아웃에 사용 가능한 공간 정보를 전달하는 데 사용됩니다.
LayoutConstraints
객체는 availableInlineSize
및 availableBlockSize
속성을 가집니다. 이는 레이아웃이 준수해야 하는 LayoutFragment
의
사용 가능한 공간을
나타냅니다.
참고: 일부 레이아웃은 이 크기를 초과하는 LayoutFragment
를
생성해야 할 수 있습니다. 예를 들어 대체 요소 등입니다.
부모 레이아웃은 이러한 상황을 예상하고 적절하게 처리해야 합니다.
부모 레이아웃은 현재 레이아웃이 특정 크기를 정확히 갖도록 요구할 수 있습니다. fixedInlineSize
또는 fixedBlockSize
가
지정된 경우 현재 레이아웃은 해당 방향에서 지정된 크기로
LayoutFragment
를
생성해야 합니다.
registerLayout('flex-distribution-like', class { *intrinsicSizes(children, edges, styleMap) { const childrenSizes = yield children.map((child) => { return child.intrinsicSizes(); }); const maxContentSize = childrenSizes.reduce((sum, childSizes) => { return sum + childSizes.maxContentSize; }, 0) + edges.all.inline; const minContentSize = childrenSizes.reduce((max, childSizes) => { return sum + childSizes.minContentSize; }, 0) + edges.all.inline; return {maxContentSize, minContentSize}; } *layout(children, edges, constraints, styleMap) { const availableInlineSize = constraints.fixedInlineSize - edges.all.inline; const availableBlockSize = (constraints.fixedInlineSize || Infinity) - edges.all.block; const childConstraints = { availableInlineSize, availableBlockSize }; const unconstrainedChildFragments = yield children.map((child) => { return child.layoutNextFragment(childConstraints); }); const unconstrainedSizes = []; const totalSize = unconstrainedChildFragments.reduce((sum, fragment, i) => { unconstrainedSizes[i] = fragment.inlineSize; return sum + fragment.inlineSize; }, 0); // 자식들 사이에 여분 공간을 분배합니다. const remainingSpace = Math.max(0, inlineSize - totalSize); const extraSpace = remainingSpace / children.length; const childFragments = yield children.map((child, i) => { return child.layoutNextFragment({ fixedInlineSize: unconstrainedSizes[i] + extraSpace, availableBlockSize }); }); // 프래그먼트의 위치를 지정합니다. let inlineOffset = 0; let maxChildBlockSize = 0; for (let fragment of childFragments) { fragment.inlineOffset = inlineOffset; fragment.blockOffset = edges.all.blockStart; inlineOffset += fragment.inlineSize; maxChildBlockSize = Math.max(maxChildBlockSize, fragment.blockSize); } return { autoBlockSize: maxChildBlockSize + edges.all.block, childFragments, }; } });
LayoutConstraints
객체는 percentageInlineSize
및 percentageBlockSize
속성을 가집니다. 이는 레이아웃을 수행할 때 퍼센트 값을 해석할 기준이 되는 크기를 나타냅니다.
LayoutConstraints
객체는 blockFragmentationType
속성을 가집니다. 현재 레이아웃은 가능하다면 LayoutFragment
를
blockFragmentationOffset
에서
프래그먼트화해야 합니다.
현재 레이아웃은 blockFragmentationType
에
따라 LayoutChild
를
프래그먼트하지 않을 수도 있습니다. 예를 들어, 자식에 break-inside:
avoid-page;와 같은 속성이 있을 경우입니다.
-
sizingMode가
"block-like"
인 경우:-
fixedInlineSize를 box의 border-box 인라인 크기로 계산합니다(box의 작성 모드 기준, 블록 컨테이너와 동일하게).
-
fixedBlockSize를 box의 블록 크기가 auto이면 null로, 아니면 블록 컨테이너와 동일하게 border-box 블록 크기로 계산합니다.
-
다음과 같이 새
LayoutConstraints
객체를 반환합니다:-
fixedInlineSize
와availableInlineSize
는 fixedInlineSize로 설정 -
percentageInlineSize
는 internalLayoutConstraints의 인라인 축 퍼센트 해석 크기로 설정(box의 작성 모드 기준) -
fixedBlockSize
는 fixedBlockSize로 설정 -
availableBlockSize
는 null이 아니면 fixedBlockSize로, 아니면 internalLayoutConstraints의 블록 축 사용 가능한 공간으로 설정(box의 작성 모드 기준) -
percentageBlockSize
는 internalLayoutConstraints의 블록 축 퍼센트 해석 크기로 설정(box의 작성 모드 기준)
-
-
-
sizingMode가
"manual"
인 경우:-
다음과 같이 새
LayoutConstraints
객체를 반환합니다:-
fixedInlineSize
/fixedBlockSize
는 internalLayoutConstraints의 고정 인라인/블록 크기로 설정(box의 작성 모드 기준, 부모 레이아웃에 의해 지정됨). 둘 중 하나는 null일 수 있음.참고: 이 경우가 발생하는 다양한 시나리오는 §4.1 크기 지정을 참고하세요.
-
availableInlineSize
/availableBlockSize
는 internalLayoutConstraints의 사용 가능한 공간으로 설정 -
percentageInlineSize
/percentageBlockSize
는 internalLayoutConstraints의 퍼센트 해석 크기로 설정
-
-
3.5. 분할 및 프래그먼트화
[Exposed=LayoutWorklet] interfaceChildBreakToken
{ readonly attribute BreakTypebreakType
; readonly attribute LayoutChildchild
; }; [Exposed=LayoutWorklet] interfaceBreakToken
{ readonly attribute sequence<ChildBreakToken>childBreakTokens
; readonly attribute anydata
; }; dictionaryBreakTokenOptions
{ sequence<ChildBreakToken>childBreakTokens
; anydata
= null; }; enumBreakType
{"none"
,"line"
,"column"
,"page"
,"region"
};
LayoutChild
는 여러 개의 LayoutFragment
를
생성할 수 있습니다.
LayoutChild
는 blockFragmentationType
이
none이 아닐 경우 블록 방향에서 프래그먼트될 수 있습니다. 또한 인라인 레벨 콘텐츠를 나타내는 LayoutChild
는 layout
options' childDisplay
(layoutOptions
로
설정됨)가 "normal"
일 때 줄 단위로 프래그먼트화될 수 있습니다.
이후의 LayoutFragment
는
이전 LayoutFragment
의
breakToken
을
사용하여 생성됩니다.
이는 자식 레이아웃에 ChildBreakToken
에
인코딩된 지점에서 LayoutFragment
를
생성하도록 지시합니다.
이 예제는 LayoutFragment
의
이전 breakToken
을
사용하여 LayoutChild
의 다음
프래그먼트를 생성하는 방법도 보여줍니다.
또한 BreakToken
을
사용하여 LayoutConstraints
의
blockFragmentationType
을
준수하는 방법을 보여줍니다. 이전 BreakToken
에서 레이아웃을
재개합니다.
FragmentResultOptions
를
반환하며, breakToken
을
사용해 레이아웃을 재개할 수 있습니다.
registerLayout('indent-lines', class { static layoutOptions = {childDisplay: 'normal'}; static inputProperties = ['--indent', '--indent-lines']; *layout(children, edges, constraints, styleMap, breakToken) { // (내부) 사용 가능한 크기를 결정합니다. const availableInlineSize = constraints.fixedInlineSize - edges.all.inline; const availableBlockSize = (constraints.fixedBlockSize || Infinity) - edges.all.block; // 들여쓸 줄 수와 들여쓰기 값을 결정합니다. const indent = resolveLength(constraints, styleMap.get('--indent')); let lines = styleMap.get('--indent-lines').value; const childFragments = []; let childBreakToken = null; if (breakToken) { childBreakToken = breakToken.childBreakTokens[0]; // 이미 프래그먼트를 생성한 자식들을 제거합니다. children.splice(0, children.indexOf(childBreakToken.child)); } let blockOffset = edges.all.blockStart; let child = children.shift(); while (child) { const shouldIndent = lines-- > 0; // 들여쓰기 값을 적용하여 인라인 크기를 조정합니다. const childAvailableInlineSize = shouldIndent ? availableInlineSize - indent : availableInlineSize; const childConstraints = { availableInlineSize: childAvailableInlineSize, availableBlockSize, percentageInlineSize: availableInlineSize, blockFragmentationType: constraints.blockFragmentationType, }; const fragment = yield child.layoutNextFragment(childConstraints, childBreakToken); childFragments.push(fragment); // 프래그먼트의 위치를 지정합니다. fragment.inlineOffset = shouldIndent ? edges.all.inlineStart + indent : edges.all.inlineStart; fragment.blockOffset = blockOffset; blockOffset += fragment.blockSize; // 블록 프래그먼트 한계를 초과했는지 확인합니다. if (constraints.blockFragmentationType != 'none' && blockOffset > constraints.blockSize) { break; } if (fragment.breakToken) { childBreakToken = fragment.breakToken; } else { // 프래그먼트에 break token이 없으면 다음 자식으로 이동합니다. child = children.shift(); childBreakToken = null; } } const autoBlockSize = blockOffset + edges.all.blockEnd; // 프래그먼트를 반환합니다. const result = { autoBlockSize, childFragments: childFragments, } if (childBreakToken) { result.breakToken = { childBreakTokens: [childBreakToken], }; } return result; } });
3.6. 엣지
[Exposed=LayoutWorklet] interfaceLayoutEdgeSizes
{ readonly attribute doubleinlineStart
; readonly attribute doubleinlineEnd
; readonly attribute doubleblockStart
; readonly attribute doubleblockEnd
; // Convenience attributes for the sum in one direction. readonly attribute doubleinline
; readonly attribute doubleblock
; }; [Exposed=LayoutWorklet] interfaceLayoutEdges
{ readonly attribute LayoutEdgeSizesborder
; readonly attribute LayoutEdgeSizesscrollbar
; readonly attribute LayoutEdgeSizespadding
; readonly attribute LayoutEdgeSizesall
; };
LayoutEdges
객체는 레이아웃 메서드에 전달됩니다. 이는 현재 레이아웃되는 박스의 박스 모델 엣지의 크기를 나타냅니다.
LayoutEdges
는
border
,
scrollbar
,
padding
속성을 가집니다. 각각은 해당 엣지의 너비를 의미합니다.
LayoutEdges
에는
all
속성이 있습니다. 이 속성은
border
,
scrollbar
,
padding
엣지의 합입니다.
LayoutEdgeSizes
객체는 각 추상 방향별(즉,
inlineStart
,
inlineEnd
,
blockStart
,
blockEnd
)
CSS 픽셀 단위의 엣지 너비를 나타냅니다.
inline
,
block
속성은 LayoutEdgeSizes
객체에서 각 방향 합계에 대한 편의 속성입니다.
LayoutEdges
가 포함할 수
있는 내용을 보여줍니다.
<style> .container { width: 50px; height: 50px; } .box { display: layout(box-edges); padding: 10%; border: solid 2px; overflow-y: scroll; } </style> <div class="container"> <div class="box"></div> </div>
registerLayout('box-edges', class { *layout(children, edges, constraints, styleMap, breakToken) { edges.padding.inlineStart; // 5 (10% * 50px = 5px) edges.border.blockEnd; // 2 edges.scrollbar.inlineEnd; // UA에 따라, 0 또는 0보다 클 수 있음(예: 16) edges.all.block; // 14 (2 + 5 + 5 + 2) } }
4. 다른 모듈과의 상호작용
이 섹션은 다른 CSS 모듈이 CSS Layout API와 어떻게 상호작용하는지 설명합니다.
4.1. 크기 지정
사용자 에이전트는 LayoutConstraints
객체를 사용하여 현재 레이아웃에 프래그먼트의 원하는 크기를
전달해야 합니다.
사용자 에이전트가 박스의 크기를 강제로 지정하려면 fixedInlineSize
및 fixedBlockSize
속성을 사용할 수 있습니다.
레이아웃 API 컨테이너는 layout options' sizing
값(클래스의 layoutOptions
로 설정됨)에 따라 다양한 방식으로 크기 정보를 전달받을 수 있습니다.
layout options' sizing
값이 "block-like"
이면, 레이아웃 API 컨테이너에 전달되는 LayoutConstraints
는
다음과 같습니다:
-
반드시
fixedInlineSize
를 [css-sizing-3] 및 관련 서식 컨텍스트 규칙에 따라 계산/설정해야 합니다. 예:-
블록 레벨 박스가 블록 서식 컨텍스트에서 동작하는 경우, 서식 컨텍스트를 설정하는 블록 박스처럼 사이즈가 결정되며, auto 인라인 크기는 비-대체 블록 박스와 동일하게 계산됩니다.
-
인라인 레벨 박스가 인라인 서식 컨텍스트에서 동작하는 경우, 원자 인라인 박스(예: inline-block)처럼 사이즈가 결정됩니다.
-
-
반드시
fixedBlockSize
를 [css-sizing-3] 및 관련 서식 컨텍스트 규칙에 따라 계산/설정해야 합니다. 레이아웃 API 컨테이너에 auto block size가 있고 미리 결정할 수 없는 경우,fixedBlockSize
를null
로 설정해야 함.
layout options' sizing
값이 "manual"
이면, 사용자 에이전트는 미리 fixedInlineSize
및/또는 fixedBlockSize
값을 계산하지 않아야 하며,
서식 컨텍스트에 의해 특정 크기로 강제되는 경우를 제외합니다. 예:
-
레이아웃 API 컨테이너가 블록 서식 컨텍스트 내에 있고, 인플로우이며, auto 인라인 크기를 가지면, 사용자 에이전트는
fixedInlineSize
를 stretch-fit 인라인 크기로 설정해야 함.
<style> #container { width: 100px; height: 100px; box-sizing: border-box; padding: 5px; } #layout-api { display: layout(foo); margin: 0 20px; } </style> <div id="container"> <div id="layout-api"></div> </div>
4.1.1. 포지셔닝된 레이아웃 크기 지정
레이아웃 API 컨테이너가 플로우 밖
포지셔닝된 경우, 사용자 에이전트는 CSS Positioned Layout
3 §8.1 절의 절대/고정 포지션 비-대체 요소 너비, CSS Positioned Layout 3 §8.3 절의 절대/고정 포지션
비-대체 요소 높이의 포지셔닝 크기 방정식을 반드시 해결하고, 적절한 fixedInlineSize
및 fixedBlockSize
를
설정해야 합니다.
<style> #container { position: relative; width: 100px; height: 100px; } #layout-api { display: layout(foo); top: 10px; bottom: 10px; left: 10px; right: 10px; position: absolute; } </style> <div id="container"> <div id="layout-api"></div> </div>
4.2. 포지셔닝
이 명세 레벨에서는 모든 포지셔닝은 사용자 에이전트에 의해 처리됩니다.
결과적으로:
-
플로우 밖 자식은
LayoutChild
로 나타나지 않습니다. -
레이아웃 API 컨테이너는 포함 블록을 블록 컨테이너와 동일하게 형성합니다. [CSS21]
-
inlineOffset
및blockOffset
은 포지셔닝과 변환이 적용되기 전의 프래그먼트 위치를 나타냅니다. -
static position은 레이아웃 API 컨테이너의 절대 포지셔닝된 자식의 inline-start, block-start 패딩 엣지에 설정됩니다. 자식의 auto 마진은 0으로 처리됩니다.
-
"child-relative"은 저자 레이아웃에 전달되는 유일한 자식입니다. 만약 위치가 (
inlineOffset
= 20
,blockOffset
= 30
)이라면, 실제 위치는 (25
,40
)이 되고, 상대 포지셔닝은 사용자 에이전트가 처리합니다. -
"child-absolute"은
LayoutChild
로 나타나지 않으며, 사용자 에이전트가 배치 및 포지셔닝합니다. -
위 예시는 sticky 및 fixed 포지션 자식에도 유사하게 적용됩니다.
<style> #container { display: layout(foo); position: relative; /* container는 포함 블록 */ width: 100px; height: 100px; } #child-relative { position: relative; left: 5px; top: 10px; } </style> <div id="container"> <div id="child-relative"></div> <div id="child-absolute"></div> </div>
4.3. 오버플로우
스크롤 가능한 오버플로우는 이 명세 레벨에서 레이아웃 API 컨테이너에 대해 사용자 에이전트가 처리합니다.
레이아웃 API 컨테이너는 블록 컨테이너와 동일하게 스크롤 가능한 오버플로우를 계산해야 합니다.
저자의 레이아웃 API 컨테이너가 프래그먼트를 스크롤 가능한 오버플로우 영역에 배치하더라도, 상대 포지셔닝 또는 변환이 프래그먼트를 이동시켜 오버플로우가 발생하지 않을 수 있습니다.
4.4. 프래그먼트화
부모 레이아웃은 현재 레이아웃에 프래그먼트화를 요청할 수 있으며,
blockFragmentationType
및 blockFragmentationOffset
을 설정합니다.
예를 들어 [css-multicol-1] 레이아웃은 blockFragmentationType
을 "column"
으로 설정하고, blockFragmentationOffset
을 자식이 프래그먼트화되어야 하는 위치로 설정합니다.
4.5. 정렬
레이아웃 API 컨테이너의 첫/마지막
기준선(baseline) 집합은 블록 컨테이너와 동일하게 생성됩니다(참고: CSS Box
Alignment 3 §9.1 박스 기준선 결정 방법). 단, 인플로우 자식의 순서는 문서 순서가 아니라 레이아웃 메서드(즉 childFragments
)에서
반환된 순서에 따라야 합니다.
기준선 정보를 LayoutChild
에서
조회하는 방법:
const fragment = yield child.layoutNextFragment({ fixedInlineSize: availableInlineSize, baselineRequests: ['alphabetic', 'middle'], }); fragment.baselines.get('alphabetic') === 25 /* 혹은 기타 값 */;
기준선 정보를 부모 레이아웃에 제공하는 방법:
registerLayout('baseline-producing', class { *layout(children, edges, constraints, styleMap) { const result = {baselines: {}}; for (let baselineRequest of constraints.baselineRequests) { // baselineRequest === 'alphabetic', 혹은 기타 값. result.baselines[baselineRequest] = 25; } return result; } });
5. 레이아웃
이 섹션에서는 CSS Layout API가 사용자 에이전트의 레이아웃 엔진과 어떻게 상호작용하는지 설명합니다.
5.1. 개념
레이아웃 정의는 구조체로,
저자가 정의한 레이아웃(layout() 함수로 참조 가능)에 대해 LayoutWorkletGlobalScope
가
필요로 하는 정보를 설명합니다. 다음을 포함합니다:
-
클래스 생성자 — 클래스의 생성자입니다.
-
레이아웃 생성자 함수 — 레이아웃 제너레이터 함수 콜백입니다.
-
내재적 크기 생성자 함수 — 내재적 크기 제너레이터 함수 콜백입니다.
-
생성자 유효 플래그.
-
input properties — 리스트 형태의
DOMStrings
-
child input properties — 리스트 형태의
DOMStrings
-
layout options —
LayoutOptions
문서 레이아웃 정의는 구조체로, 저자가 정의한 레이아웃(layout() 함수로 참조 가능)에 대해 문서가 필요로 하는 정보를 설명합니다. 다음을 포함합니다:
-
input properties — 리스트 형태의
DOMStrings
-
child input properties — 리스트 형태의
DOMStrings
-
layout options —
LayoutOptions
5.2. 레이아웃 무효화
각 박스에는 레이아웃 유효 플래그가 연관되어 있습니다. 이 플래그는 layout-valid 또는 layout-invalid가 될 수 있으며, 처음에는 layout-invalid로 설정됩니다.
각 박스에는 내재적 크기 유효 플래그가 연관되어 있습니다. 이 플래그는 intrinsic-sizes-valid 또는 intrinsic-sizes-invalid가 될 수 있으며, 처음에는 intrinsic-sizes-invalid로 설정됩니다.
-
layoutFunction을 box의 계산된 스타일에서 display 속성의 layout() 함수로 설정합니다(존재하는 경우). 값이 다른 타입(예: grid)이면 모든 단계를 중단합니다.
-
name을 layoutFunction의 첫 번째 인자로 설정합니다.
-
documentDefinition을 문서 레이아웃 정의 가져오기의 결과로 name을 인자로 얻습니다.
문서 레이아웃 정의 가져오기가 실패하거나 documentDefinition이
"invalid"
이면 모든 단계를 중단합니다. -
inputProperties를 documentDefinition의 input properties로 설정합니다.
-
childInputProperties를 documentDefinition의 child input properties로 설정합니다.
-
inputProperties의 각 property에 대해, 해당 property의 계산된 값이 변경되었다면, 레이아웃 유효 플래그를 layout-invalid로, 내재적 크기 유효 플래그를 intrinsic-sizes-invalid로 설정합니다.
-
childInputProperties의 각 property에 대해, 해당 property의 계산된 값이 변경되었다면, 레이아웃 유효 플래그를 layout-invalid로, 내재적 크기 유효 플래그를 intrinsic-sizes-invalid로 설정합니다.
레이아웃 함수 무효화는 박스의 계산된 스타일이 재계산될 때나, 해당 박스의 자식의 계산된 스타일이 재계산될 때 반드시 실행되어야 합니다.
LayoutChild
로
표현되는 자식 박스가 박스 트리에
추가/제거되거나, 레이아웃이 무효화(계산된 스타일 변경, 또는 후손 변경 등)되어 해당 무효화가 박스 트리 상위로 전파되어야 할 때, 현재 레이아웃 유효 플래그를 layout-invalid로, 현재 내재적 크기 유효 플래그를 intrinsic-sizes-invalid로 설정합니다.
레이아웃 API 컨테이너의 계산된
스타일이 변경되고, 이 변경이 LayoutEdges
객체 내부의
값에 영향을 주는 경우, 박스의 레이아웃 유효
플래그를 layout-invalid로, 내재적 크기 유효
플래그를 intrinsic-sizes-invalid로 설정합니다.
계산된 스타일 변경이 LayoutConstraints
객체 내부의 값에 영향을 주는 경우, 박스의 내재적 크기 유효 플래그만 intrinsic-sizes-invalid로 설정합니다.
참고: LayoutConstraints
객체는 오직 레이아웃 함수에만 전달되므로, 내재적 크기를 무효화할 필요는 없습니다.
LayoutEdges
객체를 변경할
수 있습니다:
다음과 같은 속성은 LayoutConstraints
객체를 변경할 수 있습니다:
참고: 이 내용은 CSS Layout API와 관련된 레이아웃 무효화만 설명합니다. 모든 박스는 개념적으로 레이아웃 유효 플래그를 가지며, 이러한 변경은 박스 트리 전체로 전파됩니다.
5.3. 레이아웃 워클릿
layoutWorklet
속성은 레이아웃과 관련된 모든 클래스에 대해 책임지는 Worklet
에
접근할 수 있게 해줍니다.
layoutWorklet
의
worklet global scope type은 LayoutWorkletGlobalScope
입니다.
partial interface CSS {
[SameObject] readonly attribute Worklet layoutWorklet
;
};
LayoutWorkletGlobalScope
는 layoutWorklet
의
글로벌 실행 컨텍스트입니다.
[Global=(Worklet,LayoutWorklet),Exposed=LayoutWorklet] interfaceLayoutWorkletGlobalScope
: WorkletGlobalScope { void registerLayout(DOMStringname
, VoidFunctionlayoutCtor
); };
5.4. 레이아웃 등록
[Exposed=LayoutWorklet] dictionaryLayoutOptions
{ ChildDisplayTypechildDisplay
= "block"; LayoutSizingModesizing
= "block-like"; }; [Exposed=LayoutWorklet] enumChildDisplayType
{"block"
,"normal"
, }; [Exposed=LayoutWorklet] enumLayoutSizingMode
{"block-like"
,"manual"
, };
document에는 맵 형태의 문서 레이아웃 정의들이 있습니다.
처음에는 이 맵이 비어 있으며, registerLayout(name, layoutCtor)
가 호출될 때 채워집니다.
LayoutWorkletGlobalScope
에도 맵 형태의
레이아웃 정의들이 있습니다.
처음에는 이 맵이 비어 있으며, registerLayout(name, layoutCtor)
가 호출될 때 채워집니다.
박스 중 레이아웃 API 컨테이너를 나타내는 각 박스는 맵 형태의 레이아웃 클래스 인스턴스를 가집니다. 처음에는 이 맵이 비어 있으며, 사용자 에이전트가 내재적 크기 결정 또는 프래그먼트 생성을 박스에 대해 호출할 때 채워집니다.
registerLayout(name, layoutCtor)
메서드가 호출되면, 사용자 에이전트는 다음 단계를 반드시 수행해야 합니다:
-
layoutDefinitionMap을
LayoutWorkletGlobalScope
의 레이아웃 정의 맵으로 설정합니다. -
layoutDefinitionMap[name]이 존재하면, throw "InvalidModificationError" DOMException을 발생시키고 모든 단계를 중단합니다.
-
inputProperties를 빈
sequence<DOMString>
로 설정합니다. -
inputPropertiesIterable을 Get(layoutCtor, "inputProperties")의 결과로 설정합니다.
-
inputPropertiesIterable이 undefined가 아니면, inputProperties를 변환 결과로 설정합니다. 변환 중 예외가 throw 되면 예외를 다시 throw하고 모든 단계를 중단합니다.
참고: input properties getter에서 제공되는 CSS 속성 리스트는 커스텀 또는 네이티브 CSS 속성일 수 있습니다.
참고: 해당 리스트에는 shorthand도 포함될 수 있습니다.
참고: 레이아웃 클래스가 향후 호환성을 위해, 리스트에 현재 사용자 에이전트에서 유효하지 않은 속성도 포함할 수 있습니다. 예:
margin-bikeshed-property
등. -
childInputProperties를 빈
sequence<DOMString>
로 설정합니다. -
childInputPropertiesIterable을 Get(layoutCtor, "childInputProperties")의 결과로 설정합니다.
-
childInputPropertiesIterable이 undefined가 아니면, childInputProperties를 변환 결과로 설정합니다. 변환 중 예외가 throw 되면 예외를 다시 throw하고 모든 단계를 중단합니다.
-
layoutOptionsValue를 Get(layoutCtor, "layoutOptions")의 결과로 설정합니다.
-
layoutOptions를 변환 결과로 설정합니다. 변환 중 예외가 throw 되면 예외를 다시 throw하고 모든 단계를 중단합니다.
-
IsConstructor(layoutCtor)가 false이면 throw TypeError를 발생시키고 모든 단계를 중단합니다.
-
prototype을 Get(layoutCtor, "prototype")의 결과로 설정합니다.
-
Type(prototype)이 Object가 아니면 throw TypeError를 발생시키고 모든 단계를 중단합니다.
-
intrinsicSizes를 Get(prototype,
"intrinsicSizes"
)의 결과로 설정합니다. -
IsCallable(intrinsicSizes)가 false이면 throw TypeError를 발생시키고 모든 단계를 중단합니다.
-
intrinsicSizes의
[[FunctionKind]]
내부 슬롯이"generator"
가 아니면 throw TypeError를 발생시키고 모든 단계를 중단합니다. -
layout을 Get(prototype,
"layout"
)의 결과로 설정합니다. -
IsCallable(layout)이 false이면 throw TypeError를 발생시키고 모든 단계를 중단합니다.
-
layout의
[[FunctionKind]]
내부 슬롯이"generator"
가 아니면 throw TypeError를 발생시키고 모든 단계를 중단합니다. -
definition을 새로운 레이아웃 정의로 생성하며:
-
클래스 생성자는 layoutCtor
-
레이아웃 생성자 함수는 layout
-
내재적 크기 생성자 함수는 intrinsicSizes
-
생성자 유효 플래그는 true
-
input properties는 inputProperties
-
child input properties는 childInputProperties
-
layout options는 layoutOptions
-
-
Set layoutDefinitionMap[name] = definition
-
Queue a task로 다음 단계 실행:
-
documentLayoutDefinitionMap을 연결된 document의 문서 레이아웃 정의 맵으로 설정
-
documentDefinition을 새로운 문서 레이아웃 정의로 생성하며:
-
input properties는 inputProperties
-
child input properties는 childInputProperties
-
layout options는 layoutOptions
-
-
documentLayoutDefinitionMap[name]이 존재하면 다음 단계 실행:
-
existingDocumentDefinition을 get documentLayoutDefinitionMap[name]로 설정
-
existingDocumentDefinition이
"invalid"
이면 모든 단계 중단 -
existingDocumentDefinition과 documentDefinition이 동일하지 않으면(input properties, child input properties, layout options이 다르면):
Set documentLayoutDefinitionMap[name] =
"invalid"
동일 클래스가 서로 다른
inputProperties
,childInputProperties
,layoutOptions
로 등록되었음을 디버깅 콘솔에 에러로 기록
-
-
그 외에는 set documentLayoutDefinitionMap[name] = documentDefinition
-
class MyLayout { static get inputProperties() { return ['--foo']; } static get childrenInputProperties() { return ['--bar']; } static get layoutOptions() { return {childDisplay: 'normal', sizing: 'block-like'} } *intrinsicSizes(children, edges, styleMap) { // 내재적 크기 코드 } *layout(children, edges, constraints, styleMap, breakToken) { // 레이아웃 코드 } }
5.5. 레이아웃 엔진
프라미스 기반
-
에러 리포팅이 더 좋음.
-
개발자 경험이 더 좋아질 가능성 있음.
제너레이터 기반
-
더 "엄격함" - 레이아웃 연산만 수행 가능. 각 호출에서 어떤 프라미스 API가 허용되는지 제한할 필요 없음.
-
바인딩 오버헤드가 더 낮을 가능성 있음.
5.5.1. 요청 객체
[Exposed=LayoutWorklet] interfaceIntrinsicSizesRequest
{ }; [Exposed=LayoutWorklet] interfaceLayoutFragmentRequest
{ }; typedef (IntrinsicSizesRequest or LayoutFragmentRequest)LayoutFragmentRequestOrIntrinsicSizesRequest
;
IntrinsicSizesRequest
객체는 내부 슬롯을 가집니다:
-
[[layoutChild]]
LayoutChild
로, 내재적 크기를 계산해야 하는 자식입니다.
LayoutFragmentRequest
객체는 내부 슬롯을 가집니다:
-
[[layoutChild]]
LayoutChild
로, 프래그먼트를 생성해야 하는 자식입니다. -
[[layoutConstraints]]
LayoutConstraintsOptions
딕셔너리로,LayoutChild
의 레이아웃 알고리즘에 전달되는 입력 제약 조건입니다. -
[[breakToken]]
ChildBreakToken
객체로, 레이아웃을 이어서 수행해야 하는 break token입니다.
저자가 제공한 레이아웃 클래스의 layout 메서드와 intrinsicSizes 메서드는 일반 자바스크립트 함수가 아니라 제너레이터 함수입니다. 이는 사용자 에이전트가 비동기 및 병렬 레이아웃 엔진을 지원할 수 있도록 하기 위함입니다.
저자가 layoutNextFragment()
메서드를 LayoutChild
에서
호출하면, 사용자 에이전트는 저자의 코드에 즉시 LayoutFragment
를
동기적으로 반환하지 않습니다.
대신 LayoutFragmentRequest
를
반환합니다.
이는 저자에게 완전히 불투명한 객체이지만, 내부 슬롯에는 layoutNextFragment()
호출에 대한 정보가 들어있습니다.
레이아웃 제너레이터 객체에서 LayoutFragmentRequest
들이
yield될 때, 사용자 에이전트의 레이아웃 엔진은 해당 알고리즘을 다른 작업과 병렬로, 또는 다른 실행 스레드에서 비동기로 실행할 수 있습니다. 엔진에서 LayoutFragment
들이
생성되면, 사용자 에이전트는 결과 LayoutFragment
를
제너레이터 객체에 "tick"합니다.
intrinsicSizes()
메서드도 동일하게 동작합니다.
class LayoutEngine { // 이 함수는 박스 트리의 루트, LayoutConstraints 객체, BreakToken(예: 인쇄용 페이지네이션)을 받아 LayoutFragment를 생성합니다. layoutEntry(rootBox, rootPageConstraints, breakToken) { return layoutFragment({ box: rootBox, layoutConstraints: rootPageConstraints, breakToken: breakToken, }); } // 이 함수는 LayoutFragmentRequest를 받아서 해당 레이아웃 알고리즘을 호출하여 LayoutFragment를 생성합니다. layoutFragment(fragmentRequest) { const box = fragmentRequest.layoutChild; const algorithm = selectLayoutAlgorithmForBox(box); const fragmentRequestGenerator = algorithm.layout( fragmentRequest.layoutConstraints, box.children, box.styleMap, fragmentRequest.breakToken); let nextFragmentRequest = fragmentRequestGenerator.next(); while (!nextFragmentRequest.done) { // 사용자 에이전트는 프래그먼트 생성을 병렬로(다른 스레드에서) 할 수도 있습니다. 이 예제는 순차적으로 동기 실행합니다. let fragments = nextFragmentRequest.value.map(layoutFragment); // 사용자 에이전트는 다른 작업(예: GC) 전 레이아웃을 yield할 수도 있음. 이 예제는 yield 없이 동기 실행. nextFragmentRequest = fragmentRequestGenerator.next(fragments); } return nextFragmentRequest.value; // 최종 LayoutFragment 반환 } }
5.6. 레이아웃 수행
// 저자 정의 layout() 메서드의 최종 반환값입니다. dictionaryFragmentResultOptions
{ doubleinlineSize
= 0; doubleblockSize
= 0; doubleautoBlockSize
= 0; sequence<LayoutFragment>childFragments
= []; anydata
= null; BreakTokenOptionsbreakToken
= null; }; dictionaryIntrinsicSizesResultOptions
{ doublemaxContentSize
; doubleminContentSize
; };
5.6.1. 내재적 크기 결정
내재적 크기 결정 알고리즘은 사용자 에이전트가 저자 정의 레이아웃에서 박스의 내재적 크기 정보를 쿼리하는 방법을 정의합니다.
참고: 내재적 크기 결정 알고리즘은 사용자 에이전트가 이전 호출 결과를 임의 개수 캐싱해서 재사용할 수 있게 허용합니다.
-
layoutFunction을 box의 layout() 계산값의 <display-inside>로 설정합니다.
-
layoutFunction의 내재적 크기 유효 플래그가 intrinsic-sizes-valid인 경우 사용자 에이전트는 이전 호출의 내재적 크기를 사용할 수 있습니다. 이 경우 모든 단계를 중단하고 이전 값을 사용해도 됩니다.
-
layoutFunction의 내재적 크기 유효 플래그를 intrinsic-sizes-valid로 설정합니다.
-
name을 layoutFunction의 첫 번째 인자로 설정합니다.
-
documentDefinition을 문서 레이아웃 정의 가져오기 결과로 name을 인자로 받아 설정합니다.
만약 문서 레이아웃 정의 가져오기가 실패하거나 documentDefinition이
"invalid"
라면, box는 flow layout으로 폴백하고 모든 단계를 중단합니다. -
workletGlobalScope를 레이아웃
Worklet
의 WorkletGlobalScopes 리스트에서LayoutWorkletGlobalScope
로 선택합니다.사용자 에이전트는 최소 2개 이상의
LayoutWorkletGlobalScope
를 WorkletGlobalScopes 리스트에서 반드시 생성/선택해야 합니다(메모리 제약이 없는 경우).참고: 저자가 글로벌 객체나 클래스에 재생성 불가 상태를 저장하지 않도록 보장하기 위함입니다.
사용자 에이전트는 이 시점에 WorkletGlobalScope 생성을 해도 됩니다.
-
내재적 크기 콜백 호출을 name, box, childBoxes, workletGlobalScope로 실행합니다. (옵션으로 병렬로 수행 가능)
참고: 병렬 수행 시, 해당 스레드에서 사용할 수 있는 layout worklet global scope를 선택해야 합니다.
-
definition을 레이아웃 정의 가져오기 결과로 name, workletGlobalScope를 인자로 받아 설정합니다.
만약 레이아웃 정의 가져오기가 실패했다면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다.
-
layoutInstance를 레이아웃 클래스 인스턴스 가져오기 결과로 box, definition, workletGlobalScope를 인자로 받아 설정합니다.
만약 레이아웃 클래스 인스턴스 가져오기가 실패했다면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다.
-
inputProperties를 definition의 input properties로 설정합니다.
-
children을 새로운 리스트로 생성합니다.
-
각 childBox에 대해 다음을 수행:
-
layoutChild를 레이아웃 자식 가져오기 결과로 workletGlobalScope, name, childBox를 인자로 받아 설정합니다.
-
Append layoutChild를 children에 추가합니다.
-
-
edges를
LayoutEdgeSizes
로 생성하고, box의 모든 박스 모델 엣지의 계산값으로 채웁니다. -
styleMap을 새로운
StylePropertyMapReadOnly
로 생성하고, box의 inputProperties에 나열된 속성의 계산값만 담습니다. -
이 시점에 children, styleMap이 이전 호출과 동일하면, 사용자 에이전트는 이전 호출의 내재적 크기를 재사용할 수 있습니다. 이 경우 캐시된 내재적 크기를 사용하고 모든 단계를 중단합니다.
-
intrinsicSizesGeneratorFunction을 definition의 내재적 크기 생성자 함수로 설정합니다.
-
intrinsicSizesGenerator를 Invoke(intrinsicSizesGeneratorFunction, layoutInstance, «children, edges, styleMap»)의 결과로 설정합니다.
예외가 throw되면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다.
-
intrinsicSizesValue를 run a generator(intrinsicSizesGenerator,
"intrinsic-sizes"
)의 결과로 설정합니다.run a generator가 실패하면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다.
-
intrinsicSizes를 변환(intrinsicSizesValue →
IntrinsicSizesResultOptions
) 결과로 설정합니다. 예외가 throw되면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다. -
box의 내재적 크기를 다음과 같이 설정:
-
intrinsicSizes의
minContentSize
를 box의 min-content 크기로 설정 -
intrinsicSizes의
maxContentSize
를 box의 max-content 크기로 설정
-
5.6.2. 프래그먼트 생성
프래그먼트 생성 알고리즘은 사용자 에이전트가 저자 정의 레이아웃에 대해 박스의 프래그먼트를 생성하는 방법을 정의합니다.
참고: 프래그먼트 생성 알고리즘은 사용자 에이전트가 이전 호출 결과를 임의 개수 캐싱해서 재사용할 수 있게 허용합니다.
-
layoutFunction을 box의 layout() 계산값의 <display-inside>로 설정합니다.
-
layoutFunction의 레이아웃 유효 플래그가 layout-valid이면, 사용자 에이전트는 이전 호출의 내재적 크기를 사용할 수 있습니다. 이 경우 모든 단계를 중단하고 이전 값을 사용해도 됩니다.
-
layoutFunction의 레이아웃 유효 플래그를 layout-valid로 설정합니다.
-
name을 layoutFunction의 첫 번째 인자로 설정합니다.
-
documentDefinition을 문서 레이아웃 정의 가져오기 결과로 name을 인자로 받아 설정합니다.
만약 문서 레이아웃 정의 가져오기가 실패하거나 documentDefinition이
"invalid"
라면, box는 flow layout으로 폴백하고 모든 단계를 중단합니다. -
workletGlobalScope를
LayoutWorkletGlobalScope
로 두고, layoutWorklet
의 worklet의 WorkletGlobalScopes 목록에서 선택한다.사용자 에이전트는 목록 내에서 적어도 두 개 이상의
LayoutWorkletGlobalScope
를 보유하고 선택할 의무가 있다. 단, 사용자 에이전트가 메모리 제약에 있는 경우는 예외이다.참고: 저자가 글로벌 객체나 클래스에 재생성할 수 없는 상태나 상태 저장에 의존하지 않도록 하기 위함이다.
사용자 에이전트는 이 시점에서 layout
Worklet
에 대해 WorkletGlobalScope를 생성할 수도 있다. -
레이아웃 콜백 호출을 name, box, childBoxes, internalLayoutConstraints, internalBreakToken, workletGlobalScope로 실행합니다. (옵션으로 병렬로 수행 가능)
참고: 병렬 수행 시, 해당 스레드에서 사용할 수 있는 layout worklet global scope를 선택해야 합니다.
-
definition을 레이아웃 정의 가져오기 결과로 name, workletGlobalScope를 인자로 받아 설정합니다.
만약 레이아웃 정의 가져오기가 실패했다면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다.
-
layoutInstance를 레이아웃 클래스 인스턴스 가져오기 결과로 box, definition, workletGlobalScope를 인자로 받아 설정합니다.
만약 레이아웃 클래스 인스턴스 가져오기가 실패했다면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다.
-
sizingMode를 definition의 layout options의
sizing
값으로 설정합니다. -
inputProperties를 definition의 input properties로 설정합니다.
-
children을 새로운 리스트로 생성합니다.
-
각 childBox에 대해 다음을 수행:
-
layoutChild를 레이아웃 자식 가져오기 결과로 workletGlobalScope, name, childBox를 인자로 받아 설정합니다.
-
Append layoutChild를 children에 추가합니다.
-
-
edges를
LayoutEdgeSizes
로 생성하고, box의 모든 박스 모델 엣지의 계산값으로 채웁니다. -
layoutConstraints를 레이아웃 제약 객체 생성(internalLayoutConstraints, box, sizingMode) 결과로 설정합니다.
-
styleMap을 새로운
StylePropertyMapReadOnly
로 생성하고, box의 inputProperties에 나열된 속성의 계산값만 담습니다. -
breakToken을 internalBreakToken의 정보로 채워진 새
BreakToken
로 생성합니다.internalBreakToken이 null이면 breakToken도 null로 합니다.
-
이 시점에 children, styleMap, layoutConstraints, breakToken이 이전 호출과 동일하면, 사용자 에이전트는 캐시된 프래그먼트를 재사용할 수 있습니다. 이 경우 캐시된 프래그먼트를 반환하고 모든 단계를 중단합니다.
-
layoutGeneratorFunction을 definition의 레이아웃 생성자 함수로 설정합니다.
-
layoutGenerator를 Invoke(layoutGeneratorFunction, layoutInstance, «children, edges, layoutConstraints, styleMap, breakToken»)의 결과로 설정합니다.
예외가 throw되면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다.
-
fragmentValue를 run a generator(layoutGenerator,
"layout"
)의 결과로 설정합니다.run a generator가 실패하면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다.
-
fragment를 변환(fragmentValue →
FragmentResultOptions
) 결과로 설정합니다. 예외가 throw되면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다. -
각 childFragment에 대해 fragment의
childFragments
리스트에서 다음을 수행:-
childFragment의
[[generator]]
내부 슬롯이 layoutGenerator와 같지 않으면 box는 flow layout으로 폴백하고 모든 단계를 중단합니다.
-
-
sizingMode가
"block-like"
인 경우:-
다음 단계를 수행:
-
inlineSize를 layoutConstraints의
fixedInlineSize
로 설정합니다. ("block-like"
sizing에서는 반드시 값이 설정되어야 함) -
blockSize를 box의 border-box 블록 크기로(작성 모드 기준, 블록 컨테이너와 동일하게), fragment의
autoBlockSize
를 "내재적 높이"로 사용해 계산합니다.
-
-
그 외(sizingMode가
"manual"
인 경우):-
inlineSize를 fragment의
inlineSize
로 설정 -
blockSize를 fragment의
blockSize
로 설정
-
-
-
box에 대해 프래그먼트를 다음과 같이 반환:
-
인라인 크기는 inlineSize로 설정된다.
-
블록 크기는 blockSize로 설정된다.
-
자식 프래그먼트는 fragment의
childFragments
리스트로 설정된다. 순서는 페인트 순서에 영향을 주므로 중요하다 (자세한 내용은 §2 Layout API Containers 참고). fragment의 border box에 대한 위치는 저자가 지정한inlineOffset
및blockOffset
을 기반으로 한다. -
프래그먼트 분할 정보는 fragment의
breakToken
으로 설정된다. -
clonedData는 fragment의
data
에 대해 StructuredSerializeForStorage를 호출한 결과로 한다.사용자 에이전트는 clonedData를 fragment와 함께 저장해야 한다.
-
5.6.3. 유틸리티 알고리즘
이 섹션은 내재적 크기 결정 및 프래그먼트 생성 알고리즘에서 공통적으로 사용되는 알고리즘을 명세합니다.
-
documentLayoutDefinitionMap을 연결된 문서의 문서 레이아웃 정의 맵으로 설정합니다.
-
documentLayoutDefinitionMap[name]이 존재하지 않으면, 실패를 반환하고 모든 단계를 중단합니다.
-
get documentLayoutDefinitionMap[name]의 결과를 반환합니다.
-
layoutDefinitionMap을 workletGlobalScope의 레이아웃 정의 맵으로 설정합니다.
-
layoutDefinitionMap[name]이 존재하지 않으면, 다음 단계를 수행합니다:
-
Queue a task로 다음 단계를 실행합니다:
-
documentLayoutDefinitionMap을 연결된 문서의 문서 레이아웃 정의 맵으로 설정합니다.
-
Set documentLayoutDefinitionMap[name] =
"invalid"
로 설정합니다. -
사용자 에이전트는 모든
LayoutWorkletGlobalScope
에 클래스가 등록되지 않았음을 디버깅 콘솔에 에러로 기록해야 합니다.
-
-
실패를 반환하고 모든 단계를 중단합니다.
-
-
get layoutDefinitionMap[name]의 결과를 반환합니다.
-
layoutClassInstanceMap을 box의 레이아웃 클래스 인스턴스 맵으로 설정합니다.
-
layoutInstance를 get layoutClassInstanceMap[workletGlobalScope]의 결과로 설정합니다. layoutInstance가 null이면, 다음 단계를 실행합니다:
-
definition의 constructor valid flag가 false이면, 실패를 반환하고 모든 단계를 중단합니다.
-
layoutCtor를 definition의 클래스 생성자로 설정합니다.
-
layoutInstance를 Construct(layoutCtor)의 결과로 설정합니다.
construct가 예외를 발생시키면, definition의 constructor valid flag를 false로 설정하고, 실패를 반환하며 모든 단계를 중단합니다.
-
Set layoutClassInstanceMap[workletGlobalScope] = layoutInstance로 설정합니다.
-
-
layoutInstance를 반환합니다.
-
done을 boolean으로
false
로 초기화합니다. -
nextValue를 undefined로 설정합니다.
-
done이
true
가 될 때까지 다음 단계를 반복합니다:-
nextFunction을 Get(generator,
"next"
) 결과로 설정합니다. -
IsCallable(nextFunction)이 false이면 throw TypeError를 발생시키고 모든 단계를 중단합니다.
-
nextResult를 Invoke(nextFunction, generator, «nextValue»)
예외가 throw되면 실패를 반환하고 모든 단계를 중단합니다.
-
Type(nextResult)이 Object가 아니면 throw TypeError를 발생시키고 모든 단계를 중단합니다.
-
requestOrRequests를 Get(nextResult|,
"value"
) 결과로 설정합니다. -
done을 Get(nextResult,
"done"
) 결과로 설정합니다. -
GetMethod(requestOrRequests,
@@iterable
)가 존재하는 경우:-
requests를 변환(requestOrRequests →
sequence<LayoutFragmentRequestOrIntrinsicSizesRequest>
) 결과로 설정합니다.예외가 throw되면 예외를 다시 throw하고 모든 단계를 중단합니다.
-
각 request에 대해 다음을 수행:
-
result를 제너레이터 결과 생성(request, generator, generatorType) 결과로 설정합니다.
-
Append result를 results에 추가합니다.
-
-
nextValue를 results로 설정합니다.
-
계속 진행합니다.
-
request를 변환(requestOrRequests →
LayoutFragmentRequestOrIntrinsicSizesRequest
) 결과로 설정합니다.예외가 throw되면 예외를 다시 throw하고 모든 단계를 중단합니다.
-
result를 제너레이터 결과 생성(request, generator, generatorType) 결과로 설정합니다.
제너레이터 결과 생성이 실패를 반환하면, 실패를 반환하고 모든 단계를 중단합니다.
-
nextValue를 result로 설정합니다.
-
계속 진행합니다.
사용자 에이전트는 위 루프를 순서와 다르게 실행하거나 병렬로 실행할 수 있습니다. 하지만 requests와 results의 순서는 반드시 일관성을 유지해야 합니다.
참고: 이는 사용자 에이전트가 적절한 레이아웃 알고리즘을 다른 스레드에서 실행하거나 비동기로 layout 작업을 분할할 수 있게 하기 위함입니다. 병렬 실행 시, 외부 루프는 교차 스레드 작업이 모두 끝날 때까지 기다려야 하며, 저자에게 부분 결과를 반환할 수 없습니다.
-
-
Get(nextResult,
"value"
)의 결과를 반환합니다.
-
request가
IntrinsicSizesRequest
이면:-
layoutChild를 request의 내부 슬롯
[[layoutChild]]
에서 조회합니다. -
box를 layoutChild의 내부 슬롯
[[box]]
에서 조회합니다. -
box가 박스 트리에 연결되어 있지 않으면, 실패를 반환하고 모든 단계를 중단합니다.
참고: 저자가 이전 호출에서
LayoutChild
를 유지하고 있을 수 있습니다. 박스는 박스 트리에 한번만 연결되어 재사용되지 않는 것으로 가정합니다. -
internalIntrinsicSizes를 사용자 에이전트가 box의 border box min/max content 기여도 계산 결과로 설정합니다.
-
내재적 크기 객체 생성(request, internalIntrinsicSizes) 결과를 반환합니다.
-
-
request가
LayoutFragmentRequest
이고 generatorType이"layout"
이면:-
layoutChild를 request의 내부 슬롯
[[layoutChild]]
에서 조회합니다. -
box를 layoutChild의 내부 슬롯
[[box]]
에서 조회합니다. -
box가 박스 트리에 연결되어 있지 않으면, 실패를 반환하고 모든 단계를 중단합니다.
참고: 저자가 이전 호출에서
LayoutChild
를 유지하고 있을 수 있습니다. 박스는 박스 트리에 한번만 연결되어 재사용되지 않는 것으로 가정합니다. -
childLayoutConstraints를 request의 내부 슬롯
[[layoutConstraints]]
에서 조회합니다. -
childBreakToken를 request의 내부 슬롯
[[breakToken]]
에서 조회합니다. -
internalFragment를 사용자 에이전트가 box, childLayoutConstraints, childBreakToken에 기반해 프래그먼트를 생성한 결과로 설정합니다.
-
레이아웃 프래그먼트 생성(generator, request, internalFragment) 결과를 반환합니다.
-
-
위 분기가 모두 해당되지 않으면 실패를 반환합니다.
6. 보안 고려사항
이 기능들로 인해 알려진 보안 이슈는 없습니다.
7. 개인정보 보호 고려사항
이 기능들로 인해 알려진 개인정보 보호 이슈는 없습니다.