Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(600)

Unified Diff: third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp

Issue 1856373002: Only allow forced fragmentainer breaks at class A break points. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Woho! LayoutTests/printing/css2.1/page-break-after-003.html now passes. Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
index 00eac215cf1bfc6b54086ba8fb96585d5b80c25a..fb288fdd7ac9688968fc6b1b9dc4a15f057ad6e1 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
@@ -162,18 +162,23 @@ class BlockChildrenLayoutInfo {
public:
BlockChildrenLayoutInfo(LayoutBlockFlow* blockFlow, LayoutUnit beforeEdge, LayoutUnit afterEdge)
: m_marginInfo(blockFlow, beforeEdge, afterEdge)
+ , m_previousBreakAfterValue(BreakAuto)
, m_isAtFirstInFlowChild(true) { }
const MarginInfo& marginInfo() const { return m_marginInfo; }
MarginInfo& marginInfo() { return m_marginInfo; }
LayoutUnit& previousFloatLogicalBottom() { return m_previousFloatLogicalBottom; }
+ EBreak previousBreakAfterValue() const { return m_previousBreakAfterValue; }
+ void setPreviousBreakAfterValue(EBreak value) { m_previousBreakAfterValue = value; }
+
bool isAtFirstInFlowChild() const { return m_isAtFirstInFlowChild; }
void clearIsAtFirstInFlowChild() { m_isAtFirstInFlowChild = false; }
private:
MarginInfo m_marginInfo;
LayoutUnit m_previousFloatLogicalBottom;
+ EBreak m_previousBreakAfterValue;
bool m_isAtFirstInFlowChild;
};
@@ -376,7 +381,21 @@ inline bool LayoutBlockFlow::layoutBlockFlow(bool relayoutChildren, LayoutUnit &
initMaxMarginValues();
setHasMarginBeforeQuirk(style()->hasMarginBeforeQuirk());
setHasMarginAfterQuirk(style()->hasMarginAfterQuirk());
+ }
+
+ if (state.isPaginated()) {
setPaginationStrutPropagatedFromChild(LayoutUnit());
+
+ // Start with any applicable computed break-after and break-before values for this
+ // object. During child layout, breakBefore will be joined with the breakBefore value of
+ // the first in-flow child, and breakAfter will be joined with the breakAfter value of the
+ // last in-flow child. This is done in order to honor the requirement that a class A break
+ // point [1] may only exists *between* in-flow siblings (i.e. not before the first child
+ // and not after the last child).
+ //
+ // [1] https://drafts.csswg.org/css-break/#possible-breaks
+ setBreakBefore(LayoutBlock::breakBefore());
+ setBreakAfter(LayoutBlock::breakAfter());
}
LayoutUnit beforeEdge = borderBefore() + paddingBefore();
@@ -580,6 +599,30 @@ bool LayoutBlockFlow::positionAndLayoutOnceIfNeeded(LayoutBox& child, LayoutUnit
return true;
}
+bool LayoutBlockFlow::insertForcedBreakBeforeChildIfNeeded(LayoutBox& child, BlockChildrenLayoutInfo& layoutInfo)
+{
+ if (layoutInfo.isAtFirstInFlowChild()) {
+ // There's no class A break point before the first child (only *between* siblings), so
+ // steal its break value and join it with what we already have here.
+ setBreakBefore(joinFragmentainerBreakValues(breakBefore(), child.breakBefore()));
+ return false;
+ }
+
+ // Figure out if a forced break should be inserted in front of the child. If we insert a forced
+ // break, the margins on this child may not collapse with those preceding the break.
+ EBreak classABreakPointValue = child.classABreakPointValue(layoutInfo.previousBreakAfterValue());
+ if (isForcedFragmentainerBreakValue(classABreakPointValue)) {
+ layoutInfo.marginInfo().clearMargin();
+ LayoutUnit oldLogicalTop = logicalHeight();
+ LayoutUnit newLogicalTop = applyForcedBreak(oldLogicalTop, classABreakPointValue);
+ setLogicalHeight(newLogicalTop);
+ LayoutUnit paginationStrut = newLogicalTop - oldLogicalTop;
+ child.setPaginationStrut(paginationStrut);
+ return true;
+ }
+ return false;
+}
+
void LayoutBlockFlow::layoutBlockChild(LayoutBox& child, BlockChildrenLayoutInfo& layoutInfo)
{
MarginInfo& marginInfo = layoutInfo.marginInfo();
@@ -594,7 +637,7 @@ void LayoutBlockFlow::layoutBlockChild(LayoutBox& child, BlockChildrenLayoutInfo
// be correct. Only if we're wrong (when we compute the real logical top position)
// will we have to potentially relayout.
LayoutUnit estimateWithoutPagination;
- LayoutUnit logicalTopEstimate = estimateLogicalTopPosition(child, marginInfo, estimateWithoutPagination);
+ LayoutUnit logicalTopEstimate = estimateLogicalTopPosition(child, layoutInfo, estimateWithoutPagination);
// Cache our old rect so that we can dirty the proper paint invalidation rects if the child moves.
LayoutRect oldRect = child.frameRect();
@@ -609,6 +652,11 @@ void LayoutBlockFlow::layoutBlockChild(LayoutBox& child, BlockChildrenLayoutInfo
bool childIsSelfCollapsing = child.isSelfCollapsingBlock();
bool childDiscardMarginBefore = mustDiscardMarginBeforeForChild(child);
bool childDiscardMarginAfter = mustDiscardMarginAfterForChild(child);
+ bool paginated = view()->layoutState()->isPaginated();
+
+ // If there should be a forced break before the child, we need to insert it before attempting
+ // to collapse margins or apply clearance.
+ bool forcedBreakWasInserted = paginated && insertForcedBreakBeforeChildIfNeeded(child, layoutInfo);
// Now determine the correct ypos based off examination of collapsing margin
// values.
@@ -618,9 +666,11 @@ void LayoutBlockFlow::layoutBlockChild(LayoutBox& child, BlockChildrenLayoutInfo
bool childDiscardMargin = childDiscardMarginBefore || childDiscardMarginAfter;
LayoutUnit newLogicalTop = clearFloatsIfNeeded(child, marginInfo, oldPosMarginBefore, oldNegMarginBefore, logicalTopBeforeClear, childIsSelfCollapsing, childDiscardMargin);
- // Now check for pagination.
- bool paginated = view()->layoutState()->isPaginated();
- if (paginated) {
+ // If there's a forced break in front of this child, its final position has already been
+ // determined. Otherwise, see if there are other reasons for breaking before it
+ // (break-inside:avoid, or not enough space for the first piece of child content to fit in the
+ // current fragmentainer), and adjust the position accordingly.
+ if (paginated && !forcedBreakWasInserted) {
if (estimateWithoutPagination != newLogicalTop) {
// We got a new position due to clearance or margin collapsing. Before we attempt to
// paginate (which may result in the position changing again), let's try again at the
@@ -675,10 +725,9 @@ void LayoutBlockFlow::layoutBlockChild(LayoutBox& child, BlockChildrenLayoutInfo
child.invalidatePaintForOverhangingFloats(true);
if (paginated) {
- // Check for an after page/column break.
- LayoutUnit newHeight = applyAfterBreak(child, logicalHeight(), marginInfo);
- if (newHeight != size().height())
- setLogicalHeight(newHeight);
+ // Keep track of the break-after value of the child, so that it can be joined with the
+ // break-before value of the next in-flow object at the next class A break point.
+ layoutInfo.setPreviousBreakAfterValue(child.breakAfter());
}
if (child.isLayoutMultiColumnSpannerPlaceholder()) {
@@ -689,12 +738,16 @@ void LayoutBlockFlow::layoutBlockChild(LayoutBox& child, BlockChildrenLayoutInfo
LayoutUnit LayoutBlockFlow::adjustBlockChildForPagination(LayoutUnit logicalTop, LayoutBox& child, BlockChildrenLayoutInfo& layoutInfo, bool atBeforeSideOfBlock)
{
+ // Forced breaks trumps unforced ones, and if we have a forced break, we shouldn't even be here.
+ ASSERT(layoutInfo.isAtFirstInFlowChild() || !isForcedFragmentainerBreakValue(child.classABreakPointValue(layoutInfo.previousBreakAfterValue())));
+
LayoutBlockFlow* childBlockFlow = child.isLayoutBlockFlow() ? toLayoutBlockFlow(&child) : 0;
- // Calculate the pagination strut for this child. A strut may come from three sources:
+ // See if we need a soft (unforced) break in front of this child, and set the pagination strut
+ // in that case. An unforced break may come from two sources:
// 1. The first piece of content inside the child doesn't fit in the current page or column
- // 2. A forced break before the child
- // 3. The child itself is unsplittable and doesn't fit in the current page or column.
+ // 2. The child itself has breaking restrictions (break-inside:avoid, replaced content, etc.)
+ // and doesn't fully fit in the current page or column.
//
// No matter which source, if we need to insert a strut, it should always take us to the exact
// top of a page or column further ahead, or be zero.
@@ -709,15 +762,12 @@ LayoutUnit LayoutBlockFlow::adjustBlockChildForPagination(LayoutUnit logicalTop,
LayoutUnit strutFromContent = childBlockFlow ? childBlockFlow->paginationStrutPropagatedFromChild() : LayoutUnit();
LayoutUnit logicalTopWithContentStrut = logicalTop + strutFromContent;
- // If the object has a page or column break value of "before", then we should shift to the top of the next page.
- LayoutUnit logicalTopAfterForcedBreak = applyBeforeBreak(child, logicalTop);
-
// For replaced elements and scrolled elements, we want to shift them to the next page if they don't fit on the current one.
LayoutUnit logicalTopAfterUnsplittable = adjustForUnsplittableChild(child, logicalTop);
// Pick the largest offset. Tall unsplittable content may take us to a page or column further
// ahead than the next one.
- LayoutUnit logicalTopAfterPagination = std::max(logicalTopWithContentStrut, std::max(logicalTopAfterForcedBreak, logicalTopAfterUnsplittable));
+ LayoutUnit logicalTopAfterPagination = std::max(logicalTopWithContentStrut, logicalTopAfterUnsplittable);
LayoutUnit newLogicalTop = logicalTop;
if (LayoutUnit paginationStrut = logicalTopAfterPagination - logicalTop) {
ASSERT(paginationStrut > 0);
@@ -725,7 +775,7 @@ LayoutUnit LayoutBlockFlow::adjustBlockChildForPagination(LayoutUnit logicalTop,
// first in-flow child, but the child isn't flush with the content edge of its container, due to e.g. clearance,
// there's a class C break point before the child. Otherwise we should propagate the strut to our parent block,
// and attempt to break there instead. See https://drafts.csswg.org/css-break/#possible-breaks
- if (layoutInfo.isAtFirstInFlowChild() && atBeforeSideOfBlock && logicalTopAfterForcedBreak == logicalTop && allowsPaginationStrut()) {
+ if (layoutInfo.isAtFirstInFlowChild() && atBeforeSideOfBlock && allowsPaginationStrut()) {
// FIXME: Should really check if we're exceeding the page height before propagating the strut, but we don't
// have all the information to do so (the strut only has the remaining amount to push). Gecko gets this wrong too
// and pushes to the next page anyway, so not too concerned about it.
@@ -1049,7 +1099,7 @@ void LayoutBlockFlow::layoutBlockChildren(bool relayoutChildren, SubtreeLayoutSc
if (child->isOutOfFlowPositioned()) {
child->containingBlock()->insertPositionedObject(child);
- adjustPositionedBlock(*child, marginInfo);
+ adjustPositionedBlock(*child, layoutInfo);
continue;
}
if (child->isFloating()) {
@@ -1331,11 +1381,18 @@ LayoutUnit LayoutBlockFlow::collapseMargins(LayoutBox& child, MarginInfo& margin
return logicalTop;
}
-void LayoutBlockFlow::adjustPositionedBlock(LayoutBox& child, const MarginInfo& marginInfo)
+void LayoutBlockFlow::adjustPositionedBlock(LayoutBox& child, const BlockChildrenLayoutInfo& layoutInfo)
{
LayoutUnit logicalTop = logicalHeight();
+
+ // Forced breaks are only specified on in-flow objects, but auto-positioned out-of-flow objects
+ // may be affected by a break-after value of the previous in-flow object.
+ if (view()->layoutState()->isPaginated())
+ logicalTop = applyForcedBreak(logicalTop, layoutInfo.previousBreakAfterValue());
+
updateStaticInlinePositionForChild(child, logicalTop);
+ const MarginInfo& marginInfo = layoutInfo.marginInfo();
if (!marginInfo.canCollapseWithMarginBefore()) {
// Positioned blocks don't collapse margins, so add the margin provided by
// the container now. The child's own margin is added later when calculating its logical top.
@@ -1493,15 +1550,16 @@ void LayoutBlockFlow::marginBeforeEstimateForChild(LayoutBox& child, LayoutUnit&
childBlockFlow->marginBeforeEstimateForChild(*grandchildBox, positiveMarginBefore, negativeMarginBefore, discardMarginBefore);
}
-LayoutUnit LayoutBlockFlow::estimateLogicalTopPosition(LayoutBox& child, const MarginInfo& marginInfo, LayoutUnit& estimateWithoutPagination)
+LayoutUnit LayoutBlockFlow::estimateLogicalTopPosition(LayoutBox& child, const BlockChildrenLayoutInfo& layoutInfo, LayoutUnit& estimateWithoutPagination)
{
+ const MarginInfo& marginInfo = layoutInfo.marginInfo();
// FIXME: We need to eliminate the estimation of vertical position, because when it's wrong we sometimes trigger a pathological
// relayout if there are intruding floats.
LayoutUnit logicalTopEstimate = logicalHeight();
+ LayoutUnit positiveMarginBefore;
+ LayoutUnit negativeMarginBefore;
+ bool discardMarginBefore = false;
if (!marginInfo.canCollapseWithMarginBefore()) {
- LayoutUnit positiveMarginBefore;
- LayoutUnit negativeMarginBefore;
- bool discardMarginBefore = false;
if (child.selfNeedsLayout()) {
// Try to do a basic estimation of how the collapse is going to go.
marginBeforeEstimateForChild(child, positiveMarginBefore, negativeMarginBefore, discardMarginBefore);
@@ -1530,8 +1588,25 @@ LayoutUnit LayoutBlockFlow::estimateLogicalTopPosition(LayoutBox& child, const M
estimateWithoutPagination = logicalTopEstimate;
if (layoutState->isPaginated()) {
- // If the object has a page or column break value of "before", then we should shift to the top of the next page.
- logicalTopEstimate = applyBeforeBreak(child, logicalTopEstimate);
+ if (!layoutInfo.isAtFirstInFlowChild()) {
+ // Estimate the need for a forced break in front of this child. The final break policy
+ // at this class A break point isn't known until we have laid out the children of
+ // |child|. There may be forced break-before values set on first-children inside that
+ // get propagated up to the child. Just make an estimate with what we know so far.
+ EBreak breakValue = child.classABreakPointValue(layoutInfo.previousBreakAfterValue());
+ if (isForcedFragmentainerBreakValue(breakValue)) {
+ logicalTopEstimate = applyForcedBreak(logicalHeight(), breakValue);
+ // Disregard previous margins, since they will collapse with the fragmentainer
+ // boundary, due to the forced break. Only apply margins that have been specified
+ // on the child or its descendants.
+ if (!discardMarginBefore)
+ logicalTopEstimate += positiveMarginBefore - negativeMarginBefore;
+
+ // Clearance may already have taken us past the beginning of the next
+ // fragmentainer.
+ return std::max(estimateWithoutPagination, logicalTopEstimate);
+ }
+ }
// For replaced elements and scrolled elements, we want to shift them to the next page if they don't fit on the current one.
logicalTopEstimate = adjustForUnsplittableChild(child, logicalTopEstimate);
@@ -1590,6 +1665,13 @@ void LayoutBlockFlow::handleAfterSideOfBlock(LayoutBox* lastChild, LayoutUnit be
// Update our bottom collapsed margin info.
setCollapsedBottomMargin(marginInfo);
+
+ // There's no class A break point right after the last child, only *between* siblings. So
+ // propagate the break-after value, and keep looking for a class A break point (at the next
+ // in-flow block-level object), where we'll join this break-after value with the break-before
+ // value there.
+ if (view()->layoutState()->isPaginated() && lastChild)
+ setBreakAfter(joinFragmentainerBreakValues(breakAfter(), lastChild->breakAfter()));
}
void LayoutBlockFlow::setMustDiscardMarginBefore(bool value)
@@ -1707,22 +1789,43 @@ bool LayoutBlockFlow::mustSeparateMarginAfterForChild(const LayoutBox& child) co
return false;
}
-LayoutUnit LayoutBlockFlow::applyBeforeBreak(LayoutBox& child, LayoutUnit logicalOffset)
+LayoutUnit LayoutBlockFlow::applyForcedBreak(LayoutUnit logicalOffset, EBreak breakValue)
{
- if (child.hasForcedBreakBefore())
+ // TODO(mstensho): honor breakValue. There are different types of forced breaks. We currently
+ // just assume that we want to break to the top of the next fragmentainer of the fragmentation
+ // context we're in. However, we may want to find the next left or right page - even if we're
+ // inside a multicol container when printing.
+ if (isForcedFragmentainerBreakValue(breakValue))
return nextPageLogicalTop(logicalOffset, AssociateWithFormerPage);
return logicalOffset;
}
-LayoutUnit LayoutBlockFlow::applyAfterBreak(LayoutBox& child, LayoutUnit logicalOffset, MarginInfo& marginInfo)
+void LayoutBlockFlow::setBreakBefore(EBreak breakValue)
{
- if (child.hasForcedBreakAfter()) {
- // So our margin doesn't participate in the next collapsing steps.
- marginInfo.clearMargin();
+ if (breakValue != BreakAuto && !isBreakBetweenControllable(breakValue))
+ breakValue = BreakAuto;
+ if (breakValue == BreakAuto && !m_rareData)
+ return;
+ ensureRareData().m_breakBefore = breakValue;
+}
- return nextPageLogicalTop(logicalOffset, AssociateWithFormerPage);
- }
- return logicalOffset;
+void LayoutBlockFlow::setBreakAfter(EBreak breakValue)
+{
+ if (breakValue != BreakAuto && !isBreakBetweenControllable(breakValue))
+ breakValue = BreakAuto;
+ if (breakValue == BreakAuto && !m_rareData)
+ return;
+ ensureRareData().m_breakAfter = breakValue;
+}
+
+EBreak LayoutBlockFlow::breakBefore() const
+{
+ return m_rareData ? static_cast<EBreak>(m_rareData->m_breakBefore) : BreakAuto;
+}
+
+EBreak LayoutBlockFlow::breakAfter() const
+{
+ return m_rareData ? static_cast<EBreak>(m_rareData->m_breakAfter) : BreakAuto;
}
void LayoutBlockFlow::addOverflowFromFloats()
@@ -2411,6 +2514,14 @@ bool LayoutBlockFlow::positionNewFloats(LineWidth* width)
if (childBox->style()->clear() & ClearRight)
logicalTop = std::max(lowestFloatLogicalBottom(FloatingObject::FloatRight), logicalTop);
+ bool isPaginated = view()->layoutState()->isPaginated();
+ if (isPaginated && !childrenInline()) {
+ // Forced breaks are inserted at class A break points. Floats may be affected by a
+ // break-after value on the previous in-flow sibling.
+ if (LayoutBox* previousInFlowBox = childBox->previousInFlowSiblingBox())
+ logicalTop = applyForcedBreak(logicalTop, previousInFlowBox->breakAfter());
+ }
+
LayoutPoint floatLogicalLocation = computeLogicalLocationForFloat(floatingObject, logicalTop);
setLogicalLeftForFloat(floatingObject, floatLogicalLocation.x());
@@ -2419,8 +2530,6 @@ bool LayoutBlockFlow::positionNewFloats(LineWidth* width)
setLogicalTopForChild(*childBox, floatLogicalLocation.y() + marginBeforeForChild(*childBox));
SubtreeLayoutScope layoutScope(*childBox);
- LayoutState* layoutState = view()->layoutState();
- bool isPaginated = layoutState->isPaginated();
if (isPaginated && !childBox->needsLayout())
childBox->markForPaginationRelayoutIfNeeded(layoutScope);
« no previous file with comments | « third_party/WebKit/Source/core/layout/LayoutBlockFlow.h ('k') | third_party/WebKit/Source/core/layout/LayoutBox.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698