Expose minContentWidth/maxContentWidth and a callback for computing them.

This exposes the last hooks needed to implement flexbox layout. For now,
I didn't worry too much about the exact API we're exposing since this will
all change with the upcoming redesign (e.g. https://codereview.chromium.org/1093633002).

minContentWidth == the width if the element were to wrap at every wrapping point (not including border/padding)

maxContentWidth == the width if the element were to only wrap at hard wrapping points (e.g. \n inside a whitespace: pre).

R=eseidel@chromium.org

Review URL: https://codereview.chromium.org/1101793003
This commit is contained in:
Ojan Vafai 2015-04-24 13:14:41 -07:00
parent ec37fed8f9
commit fc1dff4288
10 changed files with 159 additions and 62 deletions

View File

@ -945,6 +945,32 @@ void Element::setHeight(double height)
return box->setHeight(height);
}
double Element::minContentWidth() const
{
if (RenderBox* box = renderBox())
return box->minPreferredLogicalWidth();
return 0;
}
void Element::setMinContentWidth(double width)
{
if (RenderBox* box = renderBox())
return box->setMinPreferredLogicalWidth(width);
}
double Element::maxContentWidth() const
{
if (RenderBox* box = renderBox())
return box->maxPreferredLogicalWidth();
return 0;
}
void Element::setMaxContentWidth(double width)
{
if (RenderBox* box = renderBox())
return box->setMaxPreferredLogicalWidth(width);
}
void Element::setNeedsLayout()
{
if (RenderBox* box = renderBox())
@ -962,19 +988,26 @@ LayoutCallback* Element::layoutManager() const
return m_layoutManager.get();
}
void Element::setLayoutManager(PassOwnPtr<LayoutCallback> callback)
LayoutCallback* Element::intrinsicWidthsComputer() const
{
return m_intrinsicWidthsComputer.get();
}
void Element::setLayoutManager(PassOwnPtr<LayoutCallback> layoutManager,
PassOwnPtr<LayoutCallback> intrinsicWidthsComputer)
{
bool isAlreadyCustomLayout = renderer() && renderer()->isRenderCustomLayout();
bool requiresCustomLayout = callback;
bool requiresCustomLayout = layoutManager;
if (requiresCustomLayout != isAlreadyCustomLayout) {
// We don't go through the normal reattach codepaths because
// those are all tied to changes to the RenderStyle.
markAncestorsWithChildNeedsStyleRecalc();
detach();
} else if (callback.get() != m_layoutManager) {
} else if (layoutManager.get() != m_layoutManager) {
setNeedsLayout();
}
m_layoutManager = callback;
m_layoutManager = layoutManager;
m_intrinsicWidthsComputer = intrinsicWidthsComputer;
}
void Element::childrenChanged(const ChildrenChange& change)

View File

@ -215,11 +215,19 @@ public:
double height() const;
void setHeight(double);
double minContentWidth() const;
void setMinContentWidth(double);
double maxContentWidth() const;
void setMaxContentWidth(double);
void setNeedsLayout();
void layout();
LayoutCallback* intrinsicWidthsComputer() const;
LayoutCallback* layoutManager() const;
void setLayoutManager(PassOwnPtr<LayoutCallback>);
void setLayoutManager(PassOwnPtr<LayoutCallback> layoutManager,
PassOwnPtr<LayoutCallback> intrinsicWidthsComputer);
RenderStyle* computedStyle();
@ -370,6 +378,7 @@ private:
RefPtr<ElementData> m_elementData;
OwnPtr<LayoutCallback> m_layoutManager;
OwnPtr<LayoutCallback> m_intrinsicWidthsComputer;
};
DEFINE_NODE_TYPE_CASTS(Element, isElementNode());

View File

@ -20,7 +20,7 @@
void setNeedsLayout();
void layout();
void setLayoutManager(LayoutCallback callback);
void setLayoutManager(LayoutCallback layout, LayoutCallback computeIntrinsicWidths);
// TODO(abarth): Move to Node.
readonly attribute CSSStyleDeclaration style;
@ -48,4 +48,6 @@
attribute double y;
attribute double width;
attribute double height;
attribute double minContentWidth; // Intrinsic width if all wrappable points wrap.
attribute double maxContentWidth; // Intrinsic width if no wrappable points wrap.
};

View File

@ -388,6 +388,16 @@ LayoutUnit RenderBox::maxPreferredLogicalWidth() const
return m_maxPreferredLogicalWidth;
}
void RenderBox::setMinPreferredLogicalWidth(LayoutUnit width)
{
m_minPreferredLogicalWidth = width;
}
void RenderBox::setMaxPreferredLogicalWidth(LayoutUnit width)
{
m_maxPreferredLogicalWidth = width;
}
bool RenderBox::hasOverrideHeight() const
{
return m_rareData && m_rareData->m_overrideLogicalContentHeight != -1;

View File

@ -301,6 +301,9 @@ public:
virtual LayoutUnit minPreferredLogicalWidth() const override;
virtual LayoutUnit maxPreferredLogicalWidth() const override;
void setMinPreferredLogicalWidth(LayoutUnit);
void setMaxPreferredLogicalWidth(LayoutUnit);
// FIXME: We should rename these back to overrideLogicalHeight/Width and have them store
// the border-box height/width like the regular height/width accessors on RenderBox.
// Right now, these are different than contentHeight/contentWidth because they still

View File

@ -19,6 +19,13 @@ RenderCustomLayout::~RenderCustomLayout()
{
}
void RenderCustomLayout::computePreferredLogicalWidths()
{
ASSERT(node()->isElementNode());
toElement(node())->intrinsicWidthsComputer()->handleEvent();
clearPreferredLogicalWidthsDirty();
}
void RenderCustomLayout::layout()
{
ASSERT(node()->isElementNode());

View File

@ -12,7 +12,8 @@ namespace blink {
class RenderCustomLayout : public RenderBlock {
public:
explicit RenderCustomLayout(ContainerNode* node);
virtual void layout() override;
void computePreferredLogicalWidths() final;
void layout() final;
const char* renderName() const;
bool isRenderCustomLayout() const final { return true; }

View File

@ -11,6 +11,6 @@ void main() {
// don't need laying out.
// This test passes if it doesn't crash and the render tree
// has no RenderTexts.
document.querySelector('block').setLayoutManager(() => {});
document.querySelector('block').setLayoutManager(() {}, () {});
}
</script>

View File

@ -1,6 +1,7 @@
CONSOLE: unittest-suite-wait-for-done
CONSOLE: PASS: should have the right sizes after layout
CONSOLE: PASS: intrinsic sizes should apply
CONSOLE:
CONSOLE: All 1 tests passed.
CONSOLE: All 2 tests passed.
CONSOLE: unittest-suite-success
DONE

View File

@ -7,6 +7,12 @@
</parent>
</root>
<intrinsic-container>
<intrinsic>
<intrinsic-child style="width: 10px; height: 10px; background-color: lightblue;" />
</intrinsic>
</intrinsic-container>
<script>
import "../resources/third_party/unittest/unittest.dart";
import "../resources/unit.dart";
@ -17,60 +23,60 @@ import 'dart:sky';
void main() {
initUnit();
var first = true;
var parent = document.querySelector('parent');
var firstChild = parent.firstElementChild;
var secondChild = parent.lastElementChild;
var grandChild = document.querySelector('grandchild');
parent.setLayoutManager(() {
if (first) {
parent.width = 200.0;
} else {
parent.width = 150.0;
}
firstChild.width = 100.0;
firstChild.layout();
firstChild.x = 100.0;
firstChild.y = 50.0;
firstChild.height = 50.0;
// The second element correctly gets it's width from it's container.
// TODO(ojan): Change the layout method to take in availableWidth
// so code doesn't need to mess with setNeedsLayout dirty bits
// in the middle of layout and so the parent and child don't need
// to coordinate as much about expectations.
secondChild.setNeedsLayout();
secondChild.layout();
parent.height = 100.0;
});
void assertNonChangingValues() {
expect(parent.offsetHeight, equals(100));
expect(parent.offsetTop, equals(0));
expect(parent.offsetLeft, equals(0));
expect(firstChild.offsetWidth, equals(100));
expect(firstChild.offsetHeight, equals(50));
expect(firstChild.offsetTop, equals(50));
expect(firstChild.offsetLeft, equals(100));
expect(secondChild.offsetHeight, equals(25));
expect(secondChild.offsetTop, equals(0));
expect(secondChild.offsetLeft, equals(0));
expect(grandChild.offsetWidth, equals(25));
expect(grandChild.offsetHeight, equals(25));
expect(secondChild.offsetTop, equals(0));
expect(secondChild.offsetLeft, equals(0));
};
test("should have the right sizes after layout", () {
Completer completer = new Completer();
var first = true;
var parent = document.querySelector('parent');
var firstChild = parent.firstElementChild;
var secondChild = parent.lastElementChild;
var grandChild = document.querySelector('grandchild');
parent.setLayoutManager(() {
if (first) {
parent.width = 200.0;
} else {
parent.width = 150.0;
}
firstChild.width = 100.0;
firstChild.layout();
firstChild.x = 100.0;
firstChild.y = 50.0;
firstChild.height = 50.0;
// The second element correctly gets it's width from it's container.
// TODO(ojan): Change the layout method to take in availableWidth
// so code doesn't need to mess with setNeedsLayout dirty bits
// in the middle of layout and so the parent and child don't need
// to coordinate as much about expectations.
secondChild.setNeedsLayout();
secondChild.layout();
parent.height = 100.0;
}, () {});
void assertNonChangingValues() {
expect(parent.offsetHeight, equals(100));
expect(parent.offsetTop, equals(0));
expect(parent.offsetLeft, equals(0));
expect(firstChild.offsetWidth, equals(100));
expect(firstChild.offsetHeight, equals(50));
expect(firstChild.offsetTop, equals(50));
expect(firstChild.offsetLeft, equals(100));
expect(secondChild.offsetHeight, equals(25));
expect(secondChild.offsetTop, equals(0));
expect(secondChild.offsetLeft, equals(0));
expect(grandChild.offsetWidth, equals(25));
expect(grandChild.offsetHeight, equals(25));
expect(secondChild.offsetTop, equals(0));
expect(secondChild.offsetLeft, equals(0));
};
window.requestAnimationFrame((_) {
expect(parent.offsetWidth, equals(200));
expect(secondChild.offsetWidth, equals(200));
@ -86,13 +92,13 @@ void main() {
parent.setLayoutManager(() {
parent.width = 250.0;
});
}, () {});
window.requestAnimationFrame((_) {
expect(parent.offsetWidth, equals(250));
assertNonChangingValues();
parent.setLayoutManager(null);
parent.setLayoutManager(null, null);
window.requestAnimationFrame((_) {
expect(parent.offsetWidth, equals(300));
@ -123,5 +129,30 @@ void main() {
return completer.future;
});
test("intrinsic sizes should apply", () {
Completer completer = new Completer();
var intrinsic = document.querySelector('intrinsic');
var intrinsicChild = document.querySelector('intrinsic-child');
intrinsic.setLayoutManager(() {
intrinsicChild.layout();
}, () {
intrinsic.minContentWidth = intrinsicChild.minContentWidth + 5;
intrinsic.maxContentWidth = intrinsicChild.maxContentWidth + 7;
});
window.requestAnimationFrame((_) {
var container = document.querySelector('intrinsic-container');
container.style['width'] = '-webkit-min-content';
expect(container.offsetWidth, equals(15));
container.style['width'] = '-webkit-max-content';
expect(container.offsetWidth, equals(17));
completer.complete();
});
return completer.future;
});
}
</script>