Commit 9858d76f authored by Ian Kilpatrick's avatar Ian Kilpatrick Committed by Commit Bot

[css-layout-api] Adds ability to set available sizes on children.

This adds the availableInlineSize, availableBlockSize, to the layout
constriants options dictionary.

This also makes the children "shrink-to-fit" if they are inside a
custom layout pass. Additional logic was needed to *not* "shrink-to-fit"
children during the layout fallback if any occurred, so
LayoutCustomPhaseScope was added to handle this.

The tests that were added test a new BFC with inline children to
see if shrink to fitting works as expected.

Bug: 726125
Change-Id: Ic86d51579b680d871c66040f54e532bc47d79979
Reviewed-on: https://chromium-review.googlesource.com/1069252
Commit-Queue: Ian Kilpatrick <ikilpatrick@chromium.org>
Reviewed-by: default avatarMorten Stenshorne <mstensho@chromium.org>
Cr-Commit-Position: refs/heads/master@{#561602}
parent 751966ca
<!DOCTYPE html>
<html class=reftest-wait>
<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraintsoptions-availableinlinesize">
<link rel="match" href="layout-child-ref.html">
<meta name="assert" content="This test checks that setting the available inline size of children works as expected." />
<style>
.test {
writing-mode: horizontal-tb;
background: red;
margin: 10px;
width: 100px;
}
.child {
writing-mode: horizontal-tb;
visibility: hidden;
line-height: 0;
--available-inline-size: 20;
}
.inline {
display: inline-block;
height: 8px;
}
.inline-size-10 { width: 10px; }
.inline-size-30 { width: 30px; }
@supports (display: layout(test)) {
.test {
background: green;
display: layout(test);
}
}
</style>
<script src="/common/reftest-wait.js"></script>
<script src="/common/worklet-reftest.js"></script>
<div class="test">
<!-- As the inlines don't fit within 20px, we'll end up with two lines. -->
<div class="child" style="--inline-size-expected: 30; --block-size-expected: 16;">
<span class="inline inline-size-10"></span>
<span class="inline inline-size-30"></span>
</div>
<!-- The single inline doesn't take up the whole 20px, so will be shrink fitted. -->
<div class="child" style="--inline-size-expected: 10; --block-size-expected: 8;">
<span class="inline inline-size-10"></span>
</div>
<!-- Make sure the max-width property clamps the size. -->
<div class="child" style="max-width: 25px; --inline-size-expected: 25; --block-size-expected: 8;">
<span class="inline inline-size-30"></span>
</div>
<!-- Make sure the min-width property clamps the size. -->
<div class="child" style="min-width: 25px; --inline-size-expected: 25; --block-size-expected: 8;">
<span class="inline inline-size-10"></span>
</div>
</div>
<script>
importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-sizes-worklet.js'});
</script>
<!DOCTYPE html>
<html class=reftest-wait>
<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraintsoptions-availableinlinesize">
<link rel="match" href="layout-child-ref.html">
<meta name="assert" content="This test checks that setting the available inline size of children works as expected." />
<style>
.test {
writing-mode: horizontal-tb;
background: red;
margin: 10px;
width: 100px;
}
.child {
writing-mode: vertical-rl;
visibility: hidden;
line-height: 0;
--available-block-size: 20;
}
.inline {
display: inline-block;
width: 8px;
}
.inline-size-10 { height: 10px; }
.inline-size-30 { height: 30px; }
@supports (display: layout(test)) {
.test {
background: green;
display: layout(test);
}
}
</style>
<script src="/common/reftest-wait.js"></script>
<script src="/common/worklet-reftest.js"></script>
<div class="test">
<!-- As the inlines don't fit within 20px, we'll end up with two lines. -->
<div class="child" style="--block-size-expected: 30; --inline-size-expected: 16;">
<span class="inline inline-size-10"></span>
<span class="inline inline-size-30"></span>
</div>
<!-- The single inline doesn't take up the whole 20px, so will be shrink fitted. -->
<div class="child" style="--block-size-expected: 10; --inline-size-expected: 8;">
<span class="inline inline-size-10"></span>
</div>
<!-- Make sure the max-height property clamps the size. -->
<div class="child" style="max-height: 25px; --block-size-expected: 25; --inline-size-expected: 8;">
<span class="inline inline-size-30"></span>
</div>
<!-- Make sure the min-height property clamps the size. -->
<div class="child" style="min-height: 25px; --block-size-expected: 25; --inline-size-expected: 8;">
<span class="inline inline-size-10"></span>
</div>
</div>
<script>
importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-sizes-worklet.js'});
</script>
<!DOCTYPE html>
<html class=reftest-wait>
<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraintsoptions-availableinlinesize">
<link rel="match" href="layout-child-ref.html">
<meta name="assert" content="This test checks that setting the available inline size of children works as expected." />
<style>
.test {
writing-mode: vertical-rl;
background: red;
margin: 10px;
height: 100px;
}
.child {
writing-mode: horizontal-tb;
visibility: hidden;
line-height: 0;
--available-block-size: 20;
}
.inline {
display: inline-block;
height: 8px;
}
.inline-size-10 { width: 10px; }
.inline-size-30 { width: 30px; }
@supports (display: layout(test)) {
.test {
background: green;
display: layout(test);
}
}
</style>
<script src="/common/reftest-wait.js"></script>
<script src="/common/worklet-reftest.js"></script>
<div class="test">
<!-- As the inlines don't fit within 20px, we'll end up with two lines. -->
<div class="child" style="--block-size-expected: 30; --inline-size-expected: 16;">
<span class="inline inline-size-10"></span>
<span class="inline inline-size-30"></span>
</div>
<!-- The single inline doesn't take up the whole 20px, so will be shrink fitted. -->
<div class="child" style="--block-size-expected: 10; --inline-size-expected: 8;">
<span class="inline inline-size-10"></span>
</div>
<!-- Make sure the max-width property clamps the size. -->
<div class="child" style="max-width: 25px; --block-size-expected: 25; --inline-size-expected: 8;">
<span class="inline inline-size-30"></span>
</div>
<!-- Make sure the min-width property clamps the size. -->
<div class="child" style="min-width: 25px; --block-size-expected: 25; --inline-size-expected: 8;">
<span class="inline inline-size-10"></span>
</div>
</div>
<script>
importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-sizes-worklet.js'});
</script>
<!DOCTYPE html>
<html class=reftest-wait>
<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#dom-layoutconstraintsoptions-availableinlinesize">
<link rel="match" href="layout-child-ref.html">
<meta name="assert" content="This test checks that setting the available inline size of children works as expected." />
<style>
.test {
writing-mode: vertical-rl;
background: red;
margin: 10px;
height: 100px;
}
.child {
writing-mode: vertical-rl;
visibility: hidden;
line-height: 0;
--available-inline-size: 20;
}
.inline {
display: inline-block;
width: 8px;
}
.inline-size-10 { height: 10px; }
.inline-size-30 { height: 30px; }
@supports (display: layout(test)) {
.test {
background: green;
display: layout(test);
}
}
</style>
<script src="/common/reftest-wait.js"></script>
<script src="/common/worklet-reftest.js"></script>
<div class="test">
<!-- As the inlines don't fit within 20px, we'll end up with two lines. -->
<div class="child" style="--inline-size-expected: 30; --block-size-expected: 16;">
<span class="inline inline-size-10"></span>
<span class="inline inline-size-30"></span>
</div>
<!-- The single inline doesn't take up the whole 20px, so will be shrink fitted. -->
<div class="child" style="--inline-size-expected: 10; --block-size-expected: 8;">
<span class="inline inline-size-10"></span>
</div>
<!-- Make sure the max-height property clamps the size. -->
<div class="child" style="max-height: 25px; --inline-size-expected: 25; --block-size-expected: 8;">
<span class="inline inline-size-30"></span>
</div>
<!-- Make sure the min-height property clamps the size. -->
<div class="child" style="min-height: 25px; --inline-size-expected: 25; --block-size-expected: 8;">
<span class="inline inline-size-10"></span>
</div>
</div>
<script>
importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-sizes-worklet.js'});
</script>
...@@ -57,5 +57,5 @@ ...@@ -57,5 +57,5 @@
</div> </div>
<script> <script>
importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-fixed-sizes-worklet.js'}); importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-sizes-worklet.js'});
</script> </script>
...@@ -56,5 +56,5 @@ ...@@ -56,5 +56,5 @@
</div> </div>
<script> <script>
importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-fixed-sizes-worklet.js'}); importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-sizes-worklet.js'});
</script> </script>
...@@ -57,5 +57,5 @@ ...@@ -57,5 +57,5 @@
</div> </div>
<script> <script>
importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-fixed-sizes-worklet.js'}); importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-sizes-worklet.js'});
</script> </script>
...@@ -56,5 +56,5 @@ ...@@ -56,5 +56,5 @@
</div> </div>
<script> <script>
importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-fixed-sizes-worklet.js'}); importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-sizes-worklet.js'});
</script> </script>
...@@ -9,6 +9,8 @@ function parseNumber(value) { ...@@ -9,6 +9,8 @@ function parseNumber(value) {
registerLayout('test', class { registerLayout('test', class {
static get childInputProperties() { static get childInputProperties() {
return [ return [
'--available-inline-size',
'--available-block-size',
'--fixed-inline-size', '--fixed-inline-size',
'--fixed-block-size', '--fixed-block-size',
'--inline-size-expected', '--inline-size-expected',
...@@ -20,9 +22,16 @@ registerLayout('test', class { ...@@ -20,9 +22,16 @@ registerLayout('test', class {
*layout(children, edges, constraints, styleMap) { *layout(children, edges, constraints, styleMap) {
const childFragments = yield children.map((child) => { const childFragments = yield children.map((child) => {
const childConstraints = {}; const childConstraints = {};
const availableInlineSize = parseNumber(child.styleMap.get('--available-inline-size'));
const availableBlockSize = parseNumber(child.styleMap.get('--available-block-size'));
const fixedInlineSize = parseNumber(child.styleMap.get('--fixed-inline-size')); const fixedInlineSize = parseNumber(child.styleMap.get('--fixed-inline-size'));
const fixedBlockSize = parseNumber(child.styleMap.get('--fixed-block-size')); const fixedBlockSize = parseNumber(child.styleMap.get('--fixed-block-size'));
return child.layoutNextFragment({fixedInlineSize, fixedBlockSize}); return child.layoutNextFragment({
availableInlineSize,
availableBlockSize,
fixedInlineSize,
fixedBlockSize
});
}); });
const actual = childFragments.map((childFragment) => { const actual = childFragments.map((childFragment) => {
......
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
// don't want the resulting generated class to have a Layout* prefix. // don't want the resulting generated class to have a Layout* prefix.
// This name is fine, as it doesn't appear to javascript at all. // This name is fine, as it doesn't appear to javascript at all.
dictionary CustomLayoutConstraintsOptions { dictionary CustomLayoutConstraintsOptions {
// double availableInlineSize = 0; double availableInlineSize = 0;
// double availableBlockSize = 0; double availableBlockSize = 0;
double fixedInlineSize; double fixedInlineSize;
double fixedBlockSize; double fixedBlockSize;
......
...@@ -25,12 +25,6 @@ CustomLayoutFragment* CustomLayoutFragmentRequest::PerformLayout() { ...@@ -25,12 +25,6 @@ CustomLayoutFragment* CustomLayoutFragmentRequest::PerformLayout() {
LayoutBox* box = child_->GetLayoutBox(); LayoutBox* box = child_->GetLayoutBox();
const ComputedStyle& style = box->StyleRef(); const ComputedStyle& style = box->StyleRef();
// TODO(ikilpatrick): At the moment we just pretend that we are being sized
// off something which is 0x0. Additional fields inside the constraints
// object will allow the developer to override this.
box->SetOverrideContainingBlockContentLogicalWidth(LayoutUnit());
box->SetOverrideContainingBlockContentLogicalHeight(LayoutUnit());
DCHECK(box->Parent()); DCHECK(box->Parent());
DCHECK(box->Parent()->IsLayoutCustom()); DCHECK(box->Parent()->IsLayoutCustom());
DCHECK(box->Parent() == box->ContainingBlock()); DCHECK(box->Parent() == box->ContainingBlock());
...@@ -48,6 +42,14 @@ CustomLayoutFragment* CustomLayoutFragmentRequest::PerformLayout() { ...@@ -48,6 +42,14 @@ CustomLayoutFragment* CustomLayoutFragmentRequest::PerformLayout() {
box->SetOverrideLogicalHeight( box->SetOverrideLogicalHeight(
LayoutUnit::FromDoubleRound(options_.fixedInlineSize())); LayoutUnit::FromDoubleRound(options_.fixedInlineSize()));
} }
} else {
if (is_parallel_writing_mode) {
box->SetOverrideContainingBlockContentLogicalWidth(
LayoutUnit::FromDoubleRound(options_.availableInlineSize()));
} else {
box->SetOverrideContainingBlockContentLogicalHeight(
LayoutUnit::FromDoubleRound(options_.availableInlineSize()));
}
} }
if (options_.hasFixedBlockSize()) { if (options_.hasFixedBlockSize()) {
...@@ -58,6 +60,14 @@ CustomLayoutFragment* CustomLayoutFragmentRequest::PerformLayout() { ...@@ -58,6 +60,14 @@ CustomLayoutFragment* CustomLayoutFragmentRequest::PerformLayout() {
box->SetOverrideLogicalWidth( box->SetOverrideLogicalWidth(
LayoutUnit::FromDoubleRound(options_.fixedBlockSize())); LayoutUnit::FromDoubleRound(options_.fixedBlockSize()));
} }
} else {
if (is_parallel_writing_mode) {
box->SetOverrideContainingBlockContentLogicalHeight(
LayoutUnit::FromDoubleRound(options_.availableBlockSize()));
} else {
box->SetOverrideContainingBlockContentLogicalWidth(
LayoutUnit::FromDoubleRound(options_.availableBlockSize()));
}
} }
box->ForceLayout(); box->ForceLayout();
......
...@@ -18,7 +18,27 @@ ...@@ -18,7 +18,27 @@
namespace blink { namespace blink {
LayoutCustom::LayoutCustom(Element* element) : LayoutBlockFlow(element) { // This scope should be added when about to perform a web-developer defined
// layout. This sets the phase_ flag which changes how children are sized.
class LayoutCustomPhaseScope {
STACK_ALLOCATED();
public:
explicit LayoutCustomPhaseScope(LayoutCustom& layout_custom)
: layout_custom_(layout_custom) {
layout_custom_.phase_ = LayoutCustomPhase::kCustom;
}
~LayoutCustomPhaseScope() {
layout_custom_.phase_ = LayoutCustomPhase::kFallback;
}
private:
LayoutCustom& layout_custom_;
};
LayoutCustom::LayoutCustom(Element* element)
: LayoutBlockFlow(element), phase_(LayoutCustomPhase::kFallback) {
DCHECK(element); DCHECK(element);
} }
...@@ -96,6 +116,8 @@ void LayoutCustom::UpdateBlockLayout(bool relayout_children) { ...@@ -96,6 +116,8 @@ void LayoutCustom::UpdateBlockLayout(bool relayout_children) {
bool LayoutCustom::PerformLayout(bool relayout_children, bool LayoutCustom::PerformLayout(bool relayout_children,
SubtreeLayoutScope* layout_scope) { SubtreeLayoutScope* layout_scope) {
LayoutCustomPhaseScope phase_scope(*this);
// We need to fallback to block layout if we don't have a registered // We need to fallback to block layout if we don't have a registered
// definition yet. // definition yet.
if (state_ == kUnloaded) if (state_ == kUnloaded)
......
...@@ -10,11 +10,18 @@ ...@@ -10,11 +10,18 @@
namespace blink { namespace blink {
class LayoutCustomPhaseScope;
// NOTE: In the future there may be a third state "normal", this will mean that // NOTE: In the future there may be a third state "normal", this will mean that
// not everything is blockified, (e.g. root inline boxes, so that line-by-line // not everything is blockified, (e.g. root inline boxes, so that line-by-line
// layout can be performed). // layout can be performed).
enum LayoutCustomState { kUnloaded, kBlock }; enum LayoutCustomState { kUnloaded, kBlock };
// This enum is used to determine if the current layout is under control of web
// developer defined script, or during a fallback layout pass.
// Sizing of children is different between these two phases.
enum LayoutCustomPhase { kCustom, kFallback };
// The LayoutObject for elements which have "display: layout(foo);" specified. // The LayoutObject for elements which have "display: layout(foo);" specified.
// https://drafts.css-houdini.org/css-layout-api/ // https://drafts.css-houdini.org/css-layout-api/
// //
...@@ -27,6 +34,7 @@ class LayoutCustom final : public LayoutBlockFlow { ...@@ -27,6 +34,7 @@ class LayoutCustom final : public LayoutBlockFlow {
const char* GetName() const override { return "LayoutCustom"; } const char* GetName() const override { return "LayoutCustom"; }
LayoutCustomState State() const { return state_; } LayoutCustomState State() const { return state_; }
LayoutCustomPhase Phase() const { return phase_; }
bool CreatesNewFormattingContext() const override { return true; } bool CreatesNewFormattingContext() const override { return true; }
...@@ -37,6 +45,8 @@ class LayoutCustom final : public LayoutBlockFlow { ...@@ -37,6 +45,8 @@ class LayoutCustom final : public LayoutBlockFlow {
void UpdateBlockLayout(bool relayout_children) override; void UpdateBlockLayout(bool relayout_children) override;
private: private:
friend class LayoutCustomPhaseScope;
bool IsOfType(LayoutObjectType type) const override { bool IsOfType(LayoutObjectType type) const override {
return type == kLayoutObjectLayoutCustom || LayoutBlockFlow::IsOfType(type); return type == kLayoutObjectLayoutCustom || LayoutBlockFlow::IsOfType(type);
} }
...@@ -44,6 +54,7 @@ class LayoutCustom final : public LayoutBlockFlow { ...@@ -44,6 +54,7 @@ class LayoutCustom final : public LayoutBlockFlow {
bool PerformLayout(bool relayout_children, SubtreeLayoutScope*); bool PerformLayout(bool relayout_children, SubtreeLayoutScope*);
LayoutCustomState state_; LayoutCustomState state_;
LayoutCustomPhase phase_;
Persistent<CSSLayoutDefinition::Instance> instance_; Persistent<CSSLayoutDefinition::Instance> instance_;
}; };
......
...@@ -2968,6 +2968,9 @@ bool LayoutBox::SizesLogicalWidthToFitContent( ...@@ -2968,6 +2968,9 @@ bool LayoutBox::SizesLogicalWidthToFitContent(
if (IsHorizontalWritingMode() != ContainingBlock()->IsHorizontalWritingMode()) if (IsHorizontalWritingMode() != ContainingBlock()->IsHorizontalWritingMode())
return true; return true;
if (IsCustomItem())
return IsCustomItemShrinkToFit();
return false; return false;
} }
...@@ -5050,6 +5053,13 @@ bool LayoutBox::IsCustomItem() const { ...@@ -5050,6 +5053,13 @@ bool LayoutBox::IsCustomItem() const {
ToLayoutCustom(Parent())->State() == LayoutCustomState::kBlock; ToLayoutCustom(Parent())->State() == LayoutCustomState::kBlock;
} }
// LayoutCustom items are only shrink-to-fit during the web-developer defined
// layout phase (not during fallback).
bool LayoutBox::IsCustomItemShrinkToFit() const {
DCHECK(IsCustomItem());
return ToLayoutCustom(Parent())->Phase() == LayoutCustomPhase::kCustom;
}
bool LayoutBox::IsRenderedLegend() const { bool LayoutBox::IsRenderedLegend() const {
if (!IsHTMLLegendElement(GetNode())) if (!IsHTMLLegendElement(GetNode()))
return false; return false;
......
...@@ -1117,6 +1117,7 @@ class CORE_EXPORT LayoutBox : public LayoutBoxModelObject { ...@@ -1117,6 +1117,7 @@ class CORE_EXPORT LayoutBox : public LayoutBoxModelObject {
void UnmarkOrthogonalWritingModeRoot(); void UnmarkOrthogonalWritingModeRoot();
bool IsCustomItem() const; bool IsCustomItem() const;
bool IsCustomItemShrinkToFit() const;
bool IsDeprecatedFlexItem() const { bool IsDeprecatedFlexItem() const {
return !IsInline() && !IsFloatingOrOutOfFlowPositioned() && Parent() && return !IsInline() && !IsFloatingOrOutOfFlowPositioned() && Parent() &&
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment