blob: 08b29b088a75beb2fce8acc0b083f9ffc0a50034 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "core/rendering/RenderBlockFlow.h"
#include "core/page/FrameView.h"
#include "core/rendering/LayoutRepainter.h"
#include "core/rendering/RenderLayer.h"
#include "core/rendering/RenderNamedFlowThread.h"
#include "core/rendering/RenderView.h"
using namespace std;
namespace WebCore {
RenderBlockFlow::RenderBlockFlow(ContainerNode* node)
: RenderBlock(node)
{
}
RenderBlockFlow::~RenderBlockFlow()
{
}
void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight)
{
ASSERT(needsLayout());
if (isInline() && !isInlineBlockOrInlineTable()) // Inline <form>s inside various table elements can cause us to come in here. Bail.
return;
if (!relayoutChildren && simplifiedLayout())
return;
LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
if (updateLogicalWidthAndColumnWidth())
relayoutChildren = true;
clearFloats();
LayoutUnit previousHeight = logicalHeight();
// FIXME: should this start out as borderAndPaddingLogicalHeight() + scrollbarLogicalHeight(),
// for consistency with other render classes?
setLogicalHeight(0);
bool pageLogicalHeightChanged = false;
bool hasSpecifiedPageLogicalHeight = false;
checkForPaginationLogicalHeightChange(pageLogicalHeight, pageLogicalHeightChanged, hasSpecifiedPageLogicalHeight);
RenderView* renderView = view();
RenderStyle* styleToUse = style();
LayoutStateMaintainer statePusher(renderView, this, locationOffset(), hasColumns() || hasTransform() || hasReflection() || styleToUse->isFlippedBlocksWritingMode(), pageLogicalHeight, pageLogicalHeightChanged, columnInfo());
// Regions changing widths can force us to relayout our children.
RenderFlowThread* flowThread = flowThreadContainingBlock();
if (logicalWidthChangedInRegions(flowThread))
relayoutChildren = true;
if (updateRegionsAndShapesLogicalSize(flowThread))
relayoutChildren = true;
// We use four values, maxTopPos, maxTopNeg, maxBottomPos, and maxBottomNeg, to track
// our current maximal positive and negative margins. These values are used when we
// are collapsed with adjacent blocks, so for example, if you have block A and B
// collapsing together, then you'd take the maximal positive margin from both A and B
// and subtract it from the maximal negative margin from both A and B to get the
// true collapsed margin. This algorithm is recursive, so when we finish layout()
// our block knows its current maximal positive/negative values.
//
// Start out by setting our margin values to our current margins. Table cells have
// no margins, so we don't fill in the values for table cells.
bool isCell = isTableCell();
if (!isCell) {
initMaxMarginValues();
setHasMarginBeforeQuirk(styleToUse->hasMarginBeforeQuirk());
setHasMarginAfterQuirk(styleToUse->hasMarginAfterQuirk());
setPaginationStrut(0);
}
SubtreeLayoutScope layoutScope(this);
LayoutUnit repaintLogicalTop = 0;
LayoutUnit repaintLogicalBottom = 0;
LayoutUnit maxFloatLogicalBottom = 0;
if (!firstChild() && !isAnonymousBlock())
setChildrenInline(true);
if (childrenInline())
layoutInlineChildren(relayoutChildren, repaintLogicalTop, repaintLogicalBottom);
else
layoutBlockChildren(relayoutChildren, maxFloatLogicalBottom, layoutScope);
if (frameView()->partialLayout().isStopping()) {
statePusher.pop();
return;
}
// Expand our intrinsic height to encompass floats.
LayoutUnit toAdd = borderAfter() + paddingAfter() + scrollbarLogicalHeight();
if (lowestFloatLogicalBottom() > (logicalHeight() - toAdd) && expandsToEncloseOverhangingFloats())
setLogicalHeight(lowestFloatLogicalBottom() + toAdd);
if (relayoutForPagination(hasSpecifiedPageLogicalHeight, pageLogicalHeight, statePusher))
return;
// Calculate our new height.
LayoutUnit oldHeight = logicalHeight();
LayoutUnit oldClientAfterEdge = clientLogicalBottom();
// Before updating the final size of the flow thread make sure a forced break is applied after the content.
// This ensures the size information is correctly computed for the last auto-height region receiving content.
if (isRenderFlowThread())
toRenderFlowThread(this)->applyBreakAfterContent(oldClientAfterEdge);
updateLogicalHeight();
LayoutUnit newHeight = logicalHeight();
if (oldHeight != newHeight) {
if (oldHeight > newHeight && maxFloatLogicalBottom > newHeight && !childrenInline()) {
// One of our children's floats may have become an overhanging float for us. We need to look for it.
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (child->isRenderBlockFlow() && !child->isFloatingOrOutOfFlowPositioned()) {
RenderBlock* block = toRenderBlock(child);
if (block->lowestFloatLogicalBottom() + block->logicalTop() > newHeight)
addOverhangingFloats(block, false);
}
}
}
}
bool heightChanged = (previousHeight != newHeight);
if (heightChanged)
relayoutChildren = true;
layoutPositionedObjects(relayoutChildren || isRoot());
updateRegionsAndShapesAfterChildLayout(flowThread, heightChanged);
// Add overflow from children (unless we're multi-column, since in that case all our child overflow is clipped anyway).
computeOverflow(oldClientAfterEdge);
statePusher.pop();
fitBorderToLinesIfNeeded();
if (frameView()->partialLayout().isStopping())
return;
if (renderView->layoutState()->m_pageLogicalHeight)
setPageLogicalOffset(renderView->layoutState()->pageLogicalOffset(this, logicalTop()));
updateLayerTransform();
// Update our scroll information if we're overflow:auto/scroll/hidden now that we know if
// we overflow or not.
updateScrollInfoAfterLayout();
// FIXME: This repaint logic should be moved into a separate helper function!
// Repaint with our new bounds if they are different from our old bounds.
bool didFullRepaint = repainter.repaintAfterLayout();
if (!didFullRepaint && repaintLogicalTop != repaintLogicalBottom && (styleToUse->visibility() == VISIBLE || enclosingLayer()->hasVisibleContent())) {
// FIXME: We could tighten up the left and right invalidation points if we let layoutInlineChildren fill them in based off the particular lines
// it had to lay out. We wouldn't need the hasOverflowClip() hack in that case either.
LayoutUnit repaintLogicalLeft = logicalLeftVisualOverflow();
LayoutUnit repaintLogicalRight = logicalRightVisualOverflow();
if (hasOverflowClip()) {
// If we have clipped overflow, we should use layout overflow as well, since visual overflow from lines didn't propagate to our block's overflow.
// Note the old code did this as well but even for overflow:visible. The addition of hasOverflowClip() at least tightens up the hack a bit.
// layoutInlineChildren should be patched to compute the entire repaint rect.
repaintLogicalLeft = min(repaintLogicalLeft, logicalLeftLayoutOverflow());
repaintLogicalRight = max(repaintLogicalRight, logicalRightLayoutOverflow());
}
LayoutRect repaintRect;
if (isHorizontalWritingMode())
repaintRect = LayoutRect(repaintLogicalLeft, repaintLogicalTop, repaintLogicalRight - repaintLogicalLeft, repaintLogicalBottom - repaintLogicalTop);
else
repaintRect = LayoutRect(repaintLogicalTop, repaintLogicalLeft, repaintLogicalBottom - repaintLogicalTop, repaintLogicalRight - repaintLogicalLeft);
// The repaint rect may be split across columns, in which case adjustRectForColumns() will return the union.
adjustRectForColumns(repaintRect);
repaintRect.inflate(maximalOutlineSize(PaintPhaseOutline));
if (hasOverflowClip()) {
// Adjust repaint rect for scroll offset
repaintRect.move(-scrolledContentOffset());
// Don't allow this rect to spill out of our overflow box.
repaintRect.intersect(LayoutRect(LayoutPoint(), size()));
}
// Make sure the rect is still non-empty after intersecting for overflow above
if (!repaintRect.isEmpty()) {
repaintRectangle(repaintRect); // We need to do a partial repaint of our content.
if (hasReflection())
repaintRectangle(reflectedRect(repaintRect));
}
}
clearNeedsLayout();
}
void RenderBlockFlow::clearFloats()
{
if (m_floatingObjects)
m_floatingObjects->setHorizontalWritingMode(isHorizontalWritingMode());
HashSet<RenderBox*> oldIntrudingFloatSet;
if (!childrenInline() && m_floatingObjects) {
const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set();
FloatingObjectSetIterator end = floatingObjectSet.end();
for (FloatingObjectSetIterator it = floatingObjectSet.begin(); it != end; ++it) {
FloatingObject* floatingObject = *it;
if (!floatingObject->isDescendant())
oldIntrudingFloatSet.add(floatingObject->renderer());
}
}
// Inline blocks are covered by the isReplaced() check in the avoidFloats method.
if (avoidsFloats() || isRoot() || isRenderView() || isFloatingOrOutOfFlowPositioned() || isTableCell()) {
if (m_floatingObjects) {
deleteAllValues(m_floatingObjects->set());
m_floatingObjects->clear();
}
if (!oldIntrudingFloatSet.isEmpty())
markAllDescendantsWithFloatsForLayout();
return;
}
typedef HashMap<RenderObject*, FloatingObject*> RendererToFloatInfoMap;
RendererToFloatInfoMap floatMap;
if (m_floatingObjects) {
const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set();
if (childrenInline()) {
FloatingObjectSetIterator end = floatingObjectSet.end();
for (FloatingObjectSetIterator it = floatingObjectSet.begin(); it != end; ++it) {
FloatingObject* f = *it;
floatMap.add(f->renderer(), f);
}
} else {
deleteAllValues(floatingObjectSet);
}
m_floatingObjects->clear();
}
// We should not process floats if the parent node is not a RenderBlock. Otherwise, we will add
// floats in an invalid context. This will cause a crash arising from a bad cast on the parent.
// See <rdar://problem/8049753>, where float property is applied on a text node in a SVG.
if (!parent() || !parent()->isRenderBlock())
return;
// Attempt to locate a previous sibling with overhanging floats. We skip any elements that are
// out of flow (like floating/positioned elements), and we also skip over any objects that may have shifted
// to avoid floats.
RenderBlock* parentBlock = toRenderBlock(parent());
bool parentHasFloats = false;
RenderObject* prev = previousSibling();
while (prev && (prev->isFloatingOrOutOfFlowPositioned() || !prev->isBox() || !prev->isRenderBlock() || toRenderBlock(prev)->avoidsFloats())) {
if (prev->isFloating())
parentHasFloats = true;
prev = prev->previousSibling();
}
// First add in floats from the parent.
LayoutUnit logicalTopOffset = logicalTop();
if (parentHasFloats)
addIntrudingFloats(parentBlock, parentBlock->logicalLeftOffsetForContent(), logicalTopOffset);
LayoutUnit logicalLeftOffset = 0;
if (prev) {
logicalTopOffset -= toRenderBox(prev)->logicalTop();
} else {
prev = parentBlock;
logicalLeftOffset += parentBlock->logicalLeftOffsetForContent();
}
// Add overhanging floats from the previous RenderBlock, but only if it has a float that intrudes into our space.
RenderBlock* block = toRenderBlock(prev);
if (block->m_floatingObjects && block->lowestFloatLogicalBottom() > logicalTopOffset)
addIntrudingFloats(block, logicalLeftOffset, logicalTopOffset);
if (childrenInline()) {
LayoutUnit changeLogicalTop = LayoutUnit::max();
LayoutUnit changeLogicalBottom = LayoutUnit::min();
if (m_floatingObjects) {
const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set();
FloatingObjectSetIterator end = floatingObjectSet.end();
for (FloatingObjectSetIterator it = floatingObjectSet.begin(); it != end; ++it) {
FloatingObject* f = *it;
FloatingObject* oldFloatingObject = floatMap.get(f->renderer());
LayoutUnit logicalBottom = f->logicalBottom(isHorizontalWritingMode());
if (oldFloatingObject) {
LayoutUnit oldLogicalBottom = oldFloatingObject->logicalBottom(isHorizontalWritingMode());
if (f->logicalWidth(isHorizontalWritingMode()) != oldFloatingObject->logicalWidth(isHorizontalWritingMode()) || f->logicalLeft(isHorizontalWritingMode()) != oldFloatingObject->logicalLeft(isHorizontalWritingMode())) {
changeLogicalTop = 0;
changeLogicalBottom = max(changeLogicalBottom, max(logicalBottom, oldLogicalBottom));
} else {
if (logicalBottom != oldLogicalBottom) {
changeLogicalTop = min(changeLogicalTop, min(logicalBottom, oldLogicalBottom));
changeLogicalBottom = max(changeLogicalBottom, max(logicalBottom, oldLogicalBottom));
}
LayoutUnit logicalTop = f->logicalTop(isHorizontalWritingMode());
LayoutUnit oldLogicalTop = oldFloatingObject->logicalTop(isHorizontalWritingMode());
if (logicalTop != oldLogicalTop) {
changeLogicalTop = min(changeLogicalTop, min(logicalTop, oldLogicalTop));
changeLogicalBottom = max(changeLogicalBottom, max(logicalTop, oldLogicalTop));
}
}
floatMap.remove(f->renderer());
if (oldFloatingObject->originatingLine() && !selfNeedsLayout()) {
ASSERT(oldFloatingObject->originatingLine()->renderer() == this);
oldFloatingObject->originatingLine()->markDirty();
}
delete oldFloatingObject;
} else {
changeLogicalTop = 0;
changeLogicalBottom = max(changeLogicalBottom, logicalBottom);
}
}
}
RendererToFloatInfoMap::iterator end = floatMap.end();
for (RendererToFloatInfoMap::iterator it = floatMap.begin(); it != end; ++it) {
FloatingObject* floatingObject = (*it).value;
if (!floatingObject->isDescendant()) {
changeLogicalTop = 0;
changeLogicalBottom = max(changeLogicalBottom, floatingObject->logicalBottom(isHorizontalWritingMode()));
}
}
deleteAllValues(floatMap);
markLinesDirtyInBlockRange(changeLogicalTop, changeLogicalBottom);
} else if (!oldIntrudingFloatSet.isEmpty()) {
// If there are previously intruding floats that no longer intrude, then children with floats
// should also get layout because they might need their floating object lists cleared.
if (m_floatingObjects->set().size() < oldIntrudingFloatSet.size()) {
markAllDescendantsWithFloatsForLayout();
} else {
const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set();
FloatingObjectSetIterator end = floatingObjectSet.end();
for (FloatingObjectSetIterator it = floatingObjectSet.begin(); it != end && !oldIntrudingFloatSet.isEmpty(); ++it)
oldIntrudingFloatSet.remove((*it)->renderer());
if (!oldIntrudingFloatSet.isEmpty())
markAllDescendantsWithFloatsForLayout();
}
}
}
void RenderBlockFlow::layoutBlockChildren(bool relayoutChildren, LayoutUnit& maxFloatLogicalBottom, SubtreeLayoutScope& layoutScope)
{
dirtyForLayoutFromPercentageHeightDescendants(layoutScope);
LayoutUnit beforeEdge = borderBefore() + paddingBefore();
LayoutUnit afterEdge = borderAfter() + paddingAfter() + scrollbarLogicalHeight();
setLogicalHeight(beforeEdge);
// Lay out our hypothetical grid line as though it occurs at the top of the block.
if (view()->layoutState()->lineGrid() == this)
layoutLineGridBox();
// The margin struct caches all our current margin collapsing state. The compact struct caches state when we encounter compacts,
MarginInfo marginInfo(this, beforeEdge, afterEdge);
// Fieldsets need to find their legend and position it inside the border of the object.
// The legend then gets skipped during normal layout. The same is true for ruby text.
// It doesn't get included in the normal layout process but is instead skipped.
RenderObject* childToExclude = layoutSpecialExcludedChild(relayoutChildren, layoutScope);
LayoutUnit previousFloatLogicalBottom = 0;
maxFloatLogicalBottom = 0;
RenderBox* next = firstChildBox();
while (next) {
RenderBox* child = next;
next = child->nextSiblingBox();
if (childToExclude == child)
continue; // Skip this child, since it will be positioned by the specialized subclass (fieldsets and ruby runs).
updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, child);
if (child->isOutOfFlowPositioned()) {
child->containingBlock()->insertPositionedObject(child);
adjustPositionedBlock(child, marginInfo);
continue;
}
if (child->isFloating()) {
insertFloatingObject(child);
adjustFloatingBlock(marginInfo);
continue;
}
// Lay out the child.
layoutBlockChild(child, marginInfo, previousFloatLogicalBottom, maxFloatLogicalBottom);
// If doing a partial layout and the child was the target renderer, early exit here.
if (frameView()->partialLayout().checkPartialLayoutComplete(child))
break;
}
// Now do the handling of the bottom of the block, adding in our bottom border/padding and
// determining the correct collapsed bottom margin information.
handleAfterSideOfBlock(beforeEdge, afterEdge, marginInfo);
}
} // namespace WebCore