| /* |
| * 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 |