diff options
Diffstat (limited to 'Source/WebCore/rendering/RenderBlockFlow.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderBlockFlow.cpp | 2194 |
1 files changed, 1508 insertions, 686 deletions
diff --git a/Source/WebCore/rendering/RenderBlockFlow.cpp b/Source/WebCore/rendering/RenderBlockFlow.cpp index cf439e6fa..8ddcd5262 100644 --- a/Source/WebCore/rendering/RenderBlockFlow.cpp +++ b/Source/WebCore/rendering/RenderBlockFlow.cpp @@ -2,7 +2,7 @@ * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2007 David Smith (catfish.man@gmail.com) - * Copyright (C) 2003-2013 Apple Inc. All rights reserved. + * Copyright (C) 2003-2015 Apple Inc. All rights reserved. * Copyright (C) Research In Motion Limited 2010. All rights reserved. * * This library is free software; you can redistribute it and/or @@ -27,29 +27,34 @@ #include "Editor.h" #include "FloatingObjects.h" #include "Frame.h" +#include "FrameSelection.h" +#include "HTMLElement.h" +#include "HTMLInputElement.h" +#include "HTMLTextAreaElement.h" #include "HitTestLocation.h" #include "InlineTextBox.h" #include "LayoutRepainter.h" -#include "RenderFlowThread.h" +#include "Logging.h" +#include "RenderCombineText.h" +#include "RenderInline.h" #include "RenderIterator.h" #include "RenderLayer.h" +#include "RenderLineBreak.h" +#include "RenderListItem.h" +#include "RenderMarquee.h" #include "RenderMultiColumnFlowThread.h" #include "RenderMultiColumnSet.h" #include "RenderNamedFlowFragment.h" +#include "RenderNamedFlowThread.h" +#include "RenderTableCell.h" #include "RenderText.h" #include "RenderView.h" +#include "Settings.h" #include "SimpleLineLayoutFunctions.h" +#include "SimpleLineLayoutPagination.h" #include "VerticalPositionCache.h" #include "VisiblePosition.h" -#if ENABLE(CSS_SHAPES) -#include "ShapeInsideInfo.h" -#endif - -#if ENABLE(IOS_TEXT_AUTOSIZING) -#include "HTMLElement.h" -#endif - namespace WebCore { bool RenderBlock::s_canPropagateFloatIntoSibling = false; @@ -63,7 +68,7 @@ COMPILE_ASSERT(sizeof(RenderBlockFlow::MarginValues) == sizeof(LayoutUnit[4]), M COMPILE_ASSERT(sizeof(RenderBlockFlow::MarginInfo) == sizeof(SameSizeAsMarginInfo), MarginInfo_should_stay_small); // Our MarginInfo state used when laying out block children. -RenderBlockFlow::MarginInfo::MarginInfo(RenderBlockFlow& block, LayoutUnit beforeBorderPadding, LayoutUnit afterBorderPadding) +RenderBlockFlow::MarginInfo::MarginInfo(const RenderBlockFlow& block, LayoutUnit beforeBorderPadding, LayoutUnit afterBorderPadding) : m_atBeforeSideOfBlock(true) , m_atAfterSideOfBlock(false) , m_hasMarginBeforeQuirk(false) @@ -73,10 +78,7 @@ RenderBlockFlow::MarginInfo::MarginInfo(RenderBlockFlow& block, LayoutUnit befor { const RenderStyle& blockStyle = block.style(); ASSERT(block.isRenderView() || block.parent()); - m_canCollapseWithChildren = !block.isRenderView() && !block.isRoot() && !block.isOutOfFlowPositioned() - && !block.isFloating() && !block.isTableCell() && !block.hasOverflowClip() && !block.isInlineBlockOrInlineTable() - && !block.isRenderFlowThread() && !block.isWritingModeRoot() && !block.parent()->isFlexibleBox() - && blockStyle.hasAutoColumnCount() && blockStyle.hasAutoColumnWidth() && !blockStyle.columnSpan(); + m_canCollapseWithChildren = !block.createsNewFormattingContext() && !block.isRenderView(); m_canCollapseMarginBeforeWithChildren = m_canCollapseWithChildren && !beforeBorderPadding && blockStyle.marginBeforeCollapse() != MSEPARATE; @@ -95,9 +97,9 @@ RenderBlockFlow::MarginInfo::MarginInfo(RenderBlockFlow& block, LayoutUnit befor m_negativeMargin = (m_canCollapseMarginBeforeWithChildren && !block.mustDiscardMarginBefore()) ? block.maxNegativeMarginBefore() : LayoutUnit(); } -RenderBlockFlow::RenderBlockFlow(Element& element, PassRef<RenderStyle> style) - : RenderBlock(element, std::move(style), RenderBlockFlowFlag) -#if ENABLE(IOS_TEXT_AUTOSIZING) +RenderBlockFlow::RenderBlockFlow(Element& element, RenderStyle&& style) + : RenderBlock(element, WTFMove(style), RenderBlockFlowFlag) +#if ENABLE(TEXT_AUTOSIZING) , m_widthForTextAutosizing(-1) , m_lineCountForTextAutosizing(NOT_SET) #endif @@ -105,9 +107,9 @@ RenderBlockFlow::RenderBlockFlow(Element& element, PassRef<RenderStyle> style) setChildrenInline(true); } -RenderBlockFlow::RenderBlockFlow(Document& document, PassRef<RenderStyle> style) - : RenderBlock(document, std::move(style), RenderBlockFlowFlag) -#if ENABLE(IOS_TEXT_AUTOSIZING) +RenderBlockFlow::RenderBlockFlow(Document& document, RenderStyle&& style) + : RenderBlock(document, WTFMove(style), RenderBlockFlowFlag) +#if ENABLE(TEXT_AUTOSIZING) , m_widthForTextAutosizing(-1) , m_lineCountForTextAutosizing(NOT_SET) #endif @@ -117,33 +119,24 @@ RenderBlockFlow::RenderBlockFlow(Document& document, PassRef<RenderStyle> style) RenderBlockFlow::~RenderBlockFlow() { + // Do not add any code here. Add it to willBeDestroyed() instead. } void RenderBlockFlow::createMultiColumnFlowThread() { - RenderMultiColumnFlowThread* flowThread = new RenderMultiColumnFlowThread(document(), RenderStyle::createAnonymousStyleWithDisplay(&style(), BLOCK)); + RenderMultiColumnFlowThread* flowThread = new RenderMultiColumnFlowThread(document(), RenderStyle::createAnonymousStyleWithDisplay(style(), BLOCK)); flowThread->initializeStyle(); - moveAllChildrenTo(flowThread, true); + setChildrenInline(false); // Do this to avoid wrapping inline children that are just going to move into the flow thread. + deleteLines(); RenderBlock::addChild(flowThread); + flowThread->populate(); // Called after the flow thread is inserted so that we are reachable by the flow thread. setMultiColumnFlowThread(flowThread); } void RenderBlockFlow::destroyMultiColumnFlowThread() { - // Get the flow thread out of our list. - multiColumnFlowThread()->removeFromParent(); - - // Destroy all the multicolumn sets. - destroyLeftoverChildren(); - - // Move all the children of the flow thread into our block. - multiColumnFlowThread()->moveAllChildrenTo(this, true); - - // Now destroy the flow thread. - multiColumnFlowThread()->destroy(); - - // Clear the multi-column flow thread pointer. - setMultiColumnFlowThread(nullptr); + multiColumnFlowThread()->evacuateAndDestroy(); + ASSERT(!multiColumnFlowThread()); } void RenderBlockFlow::insertedIntoTree() @@ -154,61 +147,60 @@ void RenderBlockFlow::insertedIntoTree() void RenderBlockFlow::willBeDestroyed() { - // Mark as being destroyed to avoid trouble with merges in removeChild(). - m_beingDestroyed = true; - if (renderNamedFlowFragment()) - setRenderNamedFlowFragment(0); - - if (!documentBeingDestroyed()) { - if (firstChild() && firstChild()->isRunIn()) - moveRunInToOriginalPosition(*firstChild()); - } + setRenderNamedFlowFragment(nullptr); // Make sure to destroy anonymous children first while they are still connected to the rest of the tree, so that they will // properly dirty line boxes that they are removed from. Effects that do :before/:after only on hover could crash otherwise. destroyLeftoverChildren(); - // Destroy our continuation before anything other than anonymous children. - // The reason we don't destroy it before anonymous children is that they may - // have continuations of their own that are anonymous children of our continuation. - RenderBoxModelObject* continuation = this->continuation(); - if (continuation) { - continuation->destroy(); - setContinuation(0); - } - - if (!documentBeingDestroyed()) { + if (!renderTreeBeingDestroyed()) { if (firstRootBox()) { // We can't wait for RenderBox::destroy to clear the selection, // because by then we will have nuked the line boxes. - // FIXME: The FrameSelection should be responsible for this when it - // is notified of DOM mutations. if (isSelectionBorder()) - view().clearSelection(); + frame().selection().setNeedsSelectionUpdate(); // If we are an anonymous block, then our line boxes might have children // that will outlast this block. In the non-anonymous block case those // children will be destroyed by the time we return from this function. if (isAnonymousBlock()) { - for (auto box = firstRootBox(); box; box = box->nextRootBox()) { + for (auto* box = firstRootBox(); box; box = box->nextRootBox()) { while (auto childBox = box->firstChild()) childBox->removeFromParent(); } } } else if (parent()) - parent()->dirtyLinesFromChangedChild(this); + parent()->dirtyLinesFromChangedChild(*this); } m_lineBoxes.deleteLineBoxes(); - removeFromDelayedUpdateScrollInfoSet(); + blockWillBeDestroyed(); // NOTE: This jumps down to RenderBox, bypassing RenderBlock since it would do duplicate work. RenderBox::willBeDestroyed(); } -void RenderBlockFlow::clearFloats() +RenderBlockFlow* RenderBlockFlow::previousSiblingWithOverhangingFloats(bool& parentHasFloats) const +{ + // 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. + parentHasFloats = false; + for (RenderObject* sibling = previousSibling(); sibling; sibling = sibling->previousSibling()) { + if (is<RenderBlockFlow>(*sibling)) { + auto& siblingBlock = downcast<RenderBlockFlow>(*sibling); + if (!siblingBlock.avoidsFloats()) + return &siblingBlock; + } + if (sibling->isFloating()) + parentHasFloats = true; + } + return nullptr; +} + +void RenderBlockFlow::rebuildFloatingObjectSetFromIntrudingFloats() { if (m_floatingObjects) m_floatingObjects->setHorizontalWritingMode(isHorizontalWritingMode()); @@ -225,7 +217,7 @@ void RenderBlockFlow::clearFloats() } // Inline blocks are covered by the isReplaced() check in the avoidFloats method. - if (avoidsFloats() || isRoot() || isRenderView() || isFloatingOrOutOfFlowPositioned() || isTableCell()) { + if (avoidsFloats() || isDocumentElementRenderer() || isRenderView() || isFloatingOrOutOfFlowPositioned() || isTableCell()) { if (m_floatingObjects) m_floatingObjects->clear(); if (!oldIntrudingFloatSet.isEmpty()) @@ -245,39 +237,32 @@ void RenderBlockFlow::clearFloats() // 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()->isRenderBlockFlow()) + bool isBlockInsideInline = isAnonymousInlineBlock(); + if (!is<RenderBlockFlow>(parent()) && !isBlockInsideInline) 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. - RenderBlockFlow* parentBlock = toRenderBlockFlow(parent()); - bool parentHasFloats = false; - RenderObject* prev = previousSibling(); - while (prev && (prev->isFloatingOrOutOfFlowPositioned() || !prev->isBox() || !prev->isRenderBlockFlow() || toRenderBlockFlow(prev)->avoidsFloats())) { - if (prev->isFloating()) - parentHasFloats = true; - prev = prev->previousSibling(); - } - // First add in floats from the parent. Self-collapsing blocks let their parent track any floats that intrude into // them (as opposed to floats they contain themselves) so check for those here too. + RenderBlockFlow& parentBlock = downcast<RenderBlockFlow>(isBlockInsideInline ? *containingBlock() : *parent()); + bool parentHasFloats = isBlockInsideInline ? parentBlock.containsFloats() : false; + RenderBlockFlow* previousBlock = nullptr; + if (!isBlockInsideInline) + previousBlock = previousSiblingWithOverhangingFloats(parentHasFloats); LayoutUnit logicalTopOffset = logicalTop(); - if (parentHasFloats || (parentBlock->lowestFloatLogicalBottom() > logicalTopOffset && prev && toRenderBlockFlow(prev)->isSelfCollapsingBlock())) - addIntrudingFloats(parentBlock, parentBlock->logicalLeftOffsetForContent(), logicalTopOffset); + if (parentHasFloats || (parentBlock.lowestFloatLogicalBottom() > logicalTopOffset && previousBlock && previousBlock->isSelfCollapsingBlock())) + addIntrudingFloats(&parentBlock, &parentBlock, parentBlock.logicalLeftOffsetForContent(), logicalTopOffset); LayoutUnit logicalLeftOffset = 0; - if (prev) - logicalTopOffset -= toRenderBox(prev)->logicalTop(); + if (previousBlock) + logicalTopOffset -= previousBlock->logicalTop(); else { - prev = parentBlock; - logicalLeftOffset += parentBlock->logicalLeftOffsetForContent(); + previousBlock = &parentBlock; + logicalLeftOffset += parentBlock.logicalLeftOffsetForContent(); } // Add overhanging floats from the previous RenderBlock, but only if it has a float that intrudes into our space. - RenderBlockFlow* block = toRenderBlockFlow(prev); - if (block->m_floatingObjects && block->lowestFloatLogicalBottom() > logicalTopOffset) - addIntrudingFloats(block, logicalLeftOffset, logicalTopOffset); + if (previousBlock->m_floatingObjects && previousBlock->lowestFloatLogicalBottom() > logicalTopOffset) + addIntrudingFloats(previousBlock, &parentBlock, logicalLeftOffset, logicalTopOffset); if (childrenInline()) { LayoutUnit changeLogicalTop = LayoutUnit::max(); @@ -286,12 +271,12 @@ void RenderBlockFlow::clearFloats() const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto end = floatingObjectSet.end(); for (auto it = floatingObjectSet.begin(); it != end; ++it) { - FloatingObject* floatingObject = it->get(); - std::unique_ptr<FloatingObject> oldFloatingObject = floatMap.take(&floatingObject->renderer()); + const auto& floatingObject = *it->get(); + std::unique_ptr<FloatingObject> oldFloatingObject = floatMap.take(&floatingObject.renderer()); LayoutUnit logicalBottom = logicalBottomForFloat(floatingObject); if (oldFloatingObject) { - LayoutUnit oldLogicalBottom = logicalBottomForFloat(oldFloatingObject.get()); - if (logicalWidthForFloat(floatingObject) != logicalWidthForFloat(oldFloatingObject.get()) || logicalLeftForFloat(floatingObject) != logicalLeftForFloat(oldFloatingObject.get())) { + LayoutUnit oldLogicalBottom = logicalBottomForFloat(*oldFloatingObject); + if (logicalWidthForFloat(floatingObject) != logicalWidthForFloat(*oldFloatingObject) || logicalLeftForFloat(floatingObject) != logicalLeftForFloat(*oldFloatingObject)) { changeLogicalTop = 0; changeLogicalBottom = std::max(changeLogicalBottom, std::max(logicalBottom, oldLogicalBottom)); } else { @@ -300,7 +285,7 @@ void RenderBlockFlow::clearFloats() changeLogicalBottom = std::max(changeLogicalBottom, std::max(logicalBottom, oldLogicalBottom)); } LayoutUnit logicalTop = logicalTopForFloat(floatingObject); - LayoutUnit oldLogicalTop = logicalTopForFloat(oldFloatingObject.get()); + LayoutUnit oldLogicalTop = logicalTopForFloat(*oldFloatingObject); if (logicalTop != oldLogicalTop) { changeLogicalTop = std::min(changeLogicalTop, std::min(logicalTop, oldLogicalTop)); changeLogicalBottom = std::max(changeLogicalBottom, std::max(logicalTop, oldLogicalTop)); @@ -320,8 +305,8 @@ void RenderBlockFlow::clearFloats() auto end = floatMap.end(); for (auto it = floatMap.begin(); it != end; ++it) { - FloatingObject* floatingObject = it->value.get(); - if (!floatingObject->isDescendant()) { + const auto& floatingObject = *it->value.get(); + if (!floatingObject.isDescendant()) { changeLogicalTop = 0; changeLogicalBottom = std::max(changeLogicalBottom, logicalBottomForFloat(floatingObject)); } @@ -344,6 +329,142 @@ void RenderBlockFlow::clearFloats() } } +void RenderBlockFlow::adjustIntrinsicLogicalWidthsForColumns(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const +{ + if (!style().hasAutoColumnCount() || !style().hasAutoColumnWidth()) { + // The min/max intrinsic widths calculated really tell how much space elements need when + // laid out inside the columns. In order to eventually end up with the desired column width, + // we need to convert them to values pertaining to the multicol container. + int columnCount = style().hasAutoColumnCount() ? 1 : style().columnCount(); + LayoutUnit columnWidth; + LayoutUnit colGap = columnGap(); + LayoutUnit gapExtra = (columnCount - 1) * colGap; + if (style().hasAutoColumnWidth()) + minLogicalWidth = minLogicalWidth * columnCount + gapExtra; + else { + columnWidth = style().columnWidth(); + minLogicalWidth = std::min(minLogicalWidth, columnWidth); + } + // FIXME: If column-count is auto here, we should resolve it to calculate the maximum + // intrinsic width, instead of pretending that it's 1. The only way to do that is by + // performing a layout pass, but this is not an appropriate time or place for layout. The + // good news is that if height is unconstrained and there are no explicit breaks, the + // resolved column-count really should be 1. + maxLogicalWidth = std::max(maxLogicalWidth, columnWidth) * columnCount + gapExtra; + } +} + +void RenderBlockFlow::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const +{ + if (childrenInline()) + computeInlinePreferredLogicalWidths(minLogicalWidth, maxLogicalWidth); + else + computeBlockPreferredLogicalWidths(minLogicalWidth, maxLogicalWidth); + + maxLogicalWidth = std::max(minLogicalWidth, maxLogicalWidth); + + adjustIntrinsicLogicalWidthsForColumns(minLogicalWidth, maxLogicalWidth); + + if (!style().autoWrap() && childrenInline()) { + // A horizontal marquee with inline children has no minimum width. + if (layer() && layer()->marquee() && layer()->marquee()->isHorizontal()) + minLogicalWidth = 0; + } + + if (is<RenderTableCell>(*this)) { + Length tableCellWidth = downcast<RenderTableCell>(*this).styleOrColLogicalWidth(); + if (tableCellWidth.isFixed() && tableCellWidth.value() > 0) + maxLogicalWidth = std::max(minLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(tableCellWidth.value())); + } + + int scrollbarWidth = intrinsicScrollbarLogicalWidth(); + maxLogicalWidth += scrollbarWidth; + minLogicalWidth += scrollbarWidth; +} + +bool RenderBlockFlow::recomputeLogicalWidthAndColumnWidth() +{ + bool changed = recomputeLogicalWidth(); + + LayoutUnit oldColumnWidth = computedColumnWidth(); + computeColumnCountAndWidth(); + + return changed || oldColumnWidth != computedColumnWidth(); +} + +LayoutUnit RenderBlockFlow::columnGap() const +{ + if (style().hasNormalColumnGap()) + return style().fontDescription().computedPixelSize(); // "1em" is recommended as the normal gap setting. Matches <p> margins. + return style().columnGap(); +} + +void RenderBlockFlow::computeColumnCountAndWidth() +{ + // Calculate our column width and column count. + // FIXME: Can overflow on fast/block/float/float-not-removed-from-next-sibling4.html, see https://bugs.webkit.org/show_bug.cgi?id=68744 + unsigned desiredColumnCount = 1; + LayoutUnit desiredColumnWidth = contentLogicalWidth(); + + // For now, we don't support multi-column layouts when printing, since we have to do a lot of work for proper pagination. + if (document().paginated() || (style().hasAutoColumnCount() && style().hasAutoColumnWidth()) || !style().hasInlineColumnAxis()) { + setComputedColumnCountAndWidth(desiredColumnCount, desiredColumnWidth); + return; + } + + LayoutUnit availWidth = desiredColumnWidth; + LayoutUnit colGap = columnGap(); + LayoutUnit colWidth = std::max<LayoutUnit>(1, style().columnWidth()); + unsigned colCount = std::max<unsigned>(1, style().columnCount()); + + if (style().hasAutoColumnWidth() && !style().hasAutoColumnCount()) { + desiredColumnCount = colCount; + desiredColumnWidth = std::max<LayoutUnit>(0, (availWidth - ((desiredColumnCount - 1) * colGap)) / desiredColumnCount); + } else if (!style().hasAutoColumnWidth() && style().hasAutoColumnCount()) { + desiredColumnCount = std::max<unsigned>(1, ((availWidth + colGap) / (colWidth + colGap)).toUnsigned()); + desiredColumnWidth = ((availWidth + colGap) / desiredColumnCount) - colGap; + } else { + desiredColumnCount = std::max<unsigned>(std::min(colCount, ((availWidth + colGap) / (colWidth + colGap)).toUnsigned()), 1); + desiredColumnWidth = ((availWidth + colGap) / desiredColumnCount) - colGap; + } + setComputedColumnCountAndWidth(desiredColumnCount, desiredColumnWidth); +} + +bool RenderBlockFlow::willCreateColumns(std::optional<unsigned> desiredColumnCount) const +{ + // The following types are not supposed to create multicol context. + if (isFieldset() || isFileUploadControl() || isTextControl() || isListBox()) + return false; + + if (!firstChild()) + return false; + + // If overflow-y is set to paged-x or paged-y on the body or html element, we'll handle the paginating in the RenderView instead. + if ((style().overflowY() == OPAGEDX || style().overflowY() == OPAGEDY) && !(isDocumentElementRenderer() || isBody())) + return true; + + if (!style().specifiesColumns()) + return false; + + // column-axis with opposite writing direction initiates MultiColumnFlowThread. + if (!style().hasInlineColumnAxis()) + return true; + + // Non-auto column-width always initiates MultiColumnFlowThread. + if (!style().hasAutoColumnWidth()) + return true; + + if (desiredColumnCount) + return desiredColumnCount.value() > 1; + + // column-count > 1 always initiates MultiColumnFlowThread. + if (!style().hasAutoColumnCount()) + return style().columnCount() > 1; + + ASSERT_NOT_REACHED(); + return false; +} + void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight) { ASSERT(needsLayout()); @@ -353,10 +474,10 @@ void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalH LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); - if (updateLogicalWidthAndColumnWidth()) + if (recomputeLogicalWidthAndColumnWidth()) relayoutChildren = true; - clearFloats(); + rebuildFloatingObjectSetFromIntrudingFloats(); LayoutUnit previousHeight = logicalHeight(); // FIXME: should this start out as borderAndPaddingLogicalHeight() + scrollbarLogicalHeight(), @@ -364,13 +485,12 @@ void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalH setLogicalHeight(0); bool pageLogicalHeightChanged = false; - bool hasSpecifiedPageLogicalHeight = false; - checkForPaginationLogicalHeightChange(pageLogicalHeight, pageLogicalHeightChanged, hasSpecifiedPageLogicalHeight); + checkForPaginationLogicalHeightChange(relayoutChildren, pageLogicalHeight, pageLogicalHeightChanged); const RenderStyle& styleToUse = style(); - LayoutStateMaintainer statePusher(view(), *this, locationOffset(), hasColumns() || hasTransform() || hasReflection() || styleToUse.isFlippedBlocksWritingMode(), pageLogicalHeight, pageLogicalHeightChanged, columnInfo()); + LayoutStateMaintainer statePusher(view(), *this, locationOffset(), hasTransform() || hasReflection() || styleToUse.isFlippedBlocksWritingMode(), pageLogicalHeight, pageLogicalHeightChanged); - prepareShapesAndPaginationBeforeBlockLayout(relayoutChildren); + preparePaginationBeforeBlockLayout(relayoutChildren); if (!relayoutChildren) relayoutChildren = namedFlowFragmentNeedsUpdate(); @@ -405,10 +525,10 @@ void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalH // Expand our intrinsic height to encompass floats. LayoutUnit toAdd = borderAndPaddingAfter() + scrollbarLogicalHeight(); - if (lowestFloatLogicalBottom() > (logicalHeight() - toAdd) && expandsToEncloseOverhangingFloats()) + if (lowestFloatLogicalBottom() > (logicalHeight() - toAdd) && createsNewFormattingContext()) setLogicalHeight(lowestFloatLogicalBottom() + toAdd); - if (relayoutForPagination(hasSpecifiedPageLogicalHeight, pageLogicalHeight, statePusher) || relayoutToAvoidWidows(statePusher)) { + if (relayoutForPagination(statePusher) || relayoutToAvoidWidows(statePusher)) { ASSERT(!shouldBreakAtLineToAvoidWidow()); return; } @@ -419,8 +539,8 @@ void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalH // 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); + if (is<RenderFlowThread>(*this)) + downcast<RenderFlowThread>(*this).applyBreakAfterContent(oldClientAfterEdge); updateLogicalHeight(); LayoutUnit newHeight = logicalHeight(); @@ -440,9 +560,7 @@ void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalH if (heightChanged) relayoutChildren = true; - layoutPositionedObjects(relayoutChildren || isRoot()); - - updateShapesAfterBlockLayout(heightChanged); + layoutPositionedObjects(relayoutChildren || isDocumentElementRenderer()); // Add overflow from children (unless we're multi-column, since in that case all our child overflow is clipped anyway). computeOverflow(oldClientAfterEdge); @@ -482,14 +600,9 @@ void RenderBlockFlow::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalH 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()); + repaintRect.moveBy(-scrollPosition()); // Don't allow this rect to spill out of our overflow box. repaintRect.intersect(LayoutRect(LayoutPoint(), size())); @@ -563,12 +676,11 @@ void RenderBlockFlow::layoutBlockChildren(bool relayoutChildren, LayoutUnit& max void RenderBlockFlow::layoutInlineChildren(bool relayoutChildren, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom) { - if (m_lineLayoutPath == UndeterminedPath) - m_lineLayoutPath = SimpleLineLayout::canUseFor(*this) ? SimpleLinesPath : LineBoxesPath; + if (lineLayoutPath() == UndeterminedPath) + setLineLayoutPath(SimpleLineLayout::canUseFor(*this) ? SimpleLinesPath : LineBoxesPath); - if (m_lineLayoutPath == SimpleLinesPath) { - deleteLineBoxesBeforeSimpleLineLayout(); - layoutSimpleLines(repaintLogicalTop, repaintLogicalBottom); + if (lineLayoutPath() == SimpleLinesPath) { + layoutSimpleLines(relayoutChildren, repaintLogicalTop, repaintLogicalBottom); return; } @@ -582,7 +694,7 @@ void RenderBlockFlow::layoutBlockChild(RenderBox& child, MarginInfo& marginInfo, LayoutUnit oldNegMarginBefore = maxNegativeMarginBefore(); // The child is a normal flow object. Compute the margins we will use for collapsing now. - child.computeAndSetBlockDirectionMargins(this); + child.computeAndSetBlockDirectionMargins(*this); // Try to guess our correct logical top position. In most cases this guess will // be correct. Only if we're wrong (when we compute the real logical top position) @@ -597,22 +709,20 @@ void RenderBlockFlow::layoutBlockChild(RenderBox& child, MarginInfo& marginInfo, #if !ASSERT_DISABLED LayoutSize oldLayoutDelta = view().layoutDelta(); #endif - // Go ahead and position the child as though it didn't collapse with the top. + // Position the child as though it didn't collapse with the top. setLogicalTopForChild(child, logicalTopEstimate, ApplyLayoutDelta); estimateRegionRangeForBoxChild(child); - RenderBlockFlow* childBlockFlow = child.isRenderBlockFlow() ? toRenderBlockFlow(&child) : nullptr; + RenderBlockFlow* childBlockFlow = is<RenderBlockFlow>(child) ? &downcast<RenderBlockFlow>(child) : nullptr; bool markDescendantsWithFloats = false; if (logicalTopEstimate != oldLogicalTop && !child.avoidsFloats() && childBlockFlow && childBlockFlow->containsFloats()) markDescendantsWithFloats = true; -#if ENABLE(SUBPIXEL_LAYOUT) else if (UNLIKELY(logicalTopEstimate.mightBeSaturated())) // logicalTopEstimate, returned by estimateLogicalTopPosition, might be saturated for // very large elements. If it does the comparison with oldLogicalTop might yield a // false negative as adding and removing margins, borders etc from a saturated number // might yield incorrect results. If this is the case always mark for layout. markDescendantsWithFloats = true; -#endif else if (!child.avoidsFloats() || child.shrinkToAvoidFloats()) { // If an element might be affected by the presence of floats, then always mark it for // layout. @@ -628,8 +738,7 @@ void RenderBlockFlow::layoutBlockChild(RenderBox& child, MarginInfo& marginInfo, previousFloatLogicalBottom = std::max(previousFloatLogicalBottom, oldLogicalTop + childBlockFlow->lowestFloatLogicalBottom()); } - if (!child.needsLayout()) - child.markForPaginationRelayoutIfNeeded(); + child.markForPaginationRelayoutIfNeeded(); bool childHadLayout = child.everHadLayout(); bool childNeededLayout = child.needsLayout(); @@ -657,28 +766,23 @@ void RenderBlockFlow::layoutBlockChild(RenderBox& child, MarginInfo& marginInfo, // when collapseMargins dynamically adds overhanging floats because of a child with negative margins. if (logicalTopAfterClear != logicalTopEstimate || child.needsLayout() || (paginated && childBlockFlow && childBlockFlow->shouldBreakAtLineToAvoidWidow())) { if (child.shrinkToAvoidFloats()) { - // The child's width depends on the line width. - // When the child shifts to clear an item, its width can - // change (because it has more available line width). - // So go ahead and mark the item as dirty. + // The child's width depends on the line width. When the child shifts to clear an item, its width can + // change (because it has more available line width). So mark the item as dirty. child.setChildNeedsLayout(MarkOnlyThis); } if (childBlockFlow) { if (!child.avoidsFloats() && childBlockFlow->containsFloats()) childBlockFlow->markAllDescendantsWithFloatsForLayout(); - if (!child.needsLayout()) - child.markForPaginationRelayoutIfNeeded(); + child.markForPaginationRelayoutIfNeeded(); } - - // Our guess was wrong. Make the child lay itself out again. - child.layoutIfNeeded(); } - if (updateRegionRangeForBoxChild(child)) { + if (updateRegionRangeForBoxChild(child)) child.setNeedsLayout(MarkOnlyThis); - child.layoutIfNeeded(); - } + + // In case our guess was wrong, relayout the child. + child.layoutIfNeeded(); // We are no longer at the top of the block if we encounter a non-empty child. // This has to be done after checking for clear, so that margins can be reset if a clear occurred. @@ -688,13 +792,8 @@ void RenderBlockFlow::layoutBlockChild(RenderBox& child, MarginInfo& marginInfo, // Now place the child in the correct left position determineLogicalLeftPositionForChild(child, ApplyLayoutDelta); - LayoutSize childOffset = child.location() - oldRect.location(); -#if ENABLE(CSS_SHAPES) - relayoutShapeDescendantIfMoved(child.isRenderBlock() ? toRenderBlock(&child) : nullptr, childOffset); -#endif - // Update our height now that the child has been placed in the correct position. - setLogicalHeight(logicalHeight() + logicalHeightForChild(child)); + setLogicalHeight(logicalHeight() + logicalHeightForChildForFragmentation(child)); if (mustSeparateMarginAfterForChild(child)) { setLogicalHeight(logicalHeight() + marginAfterForChild(child)); marginInfo.clearMargin(); @@ -704,6 +803,7 @@ void RenderBlockFlow::layoutBlockChild(RenderBox& child, MarginInfo& marginInfo, if (childBlockFlow && childBlockFlow->containsFloats()) maxFloatLogicalBottom = std::max(maxFloatLogicalBottom, addOverhangingFloats(*childBlockFlow, !childNeededLayout)); + LayoutSize childOffset = child.location() - oldRect.location(); if (childOffset.width() || childOffset.height()) { view().addLayoutDelta(childOffset); @@ -720,6 +820,8 @@ void RenderBlockFlow::layoutBlockChild(RenderBox& child, MarginInfo& marginInfo, } if (paginated) { + if (RenderFlowThread* flowThread = flowThreadContainingBlock()) + flowThread->flowThreadDescendantBoxLaidOut(&child); // Check for an after page/column break. LayoutUnit newHeight = applyAfterBreak(child, logicalHeight(), marginInfo); if (newHeight != height()) @@ -735,7 +837,7 @@ void RenderBlockFlow::adjustPositionedBlock(RenderBox& child, const MarginInfo& bool hasStaticBlockPosition = child.style().hasStaticBlockPosition(isHorizontal); LayoutUnit logicalTop = logicalHeight(); - updateStaticInlinePositionForChild(child, logicalTop); + updateStaticInlinePositionForChild(child, logicalTop, DoNotIndentText); if (!marginInfo.canCollapseWithMarginBefore()) { // Positioned blocks don't collapse margins, so add the margin provided by @@ -756,7 +858,7 @@ void RenderBlockFlow::adjustPositionedBlock(RenderBox& child, const MarginInfo& LayoutUnit RenderBlockFlow::marginOffsetForSelfCollapsingBlock() { ASSERT(isSelfCollapsingBlock()); - RenderBlockFlow* parentBlock = toRenderBlockFlow(parent()); + RenderBlockFlow* parentBlock = downcast<RenderBlockFlow>(parent()); if (parentBlock && style().clear() && parentBlock->getClearDelta(*this, logicalHeight())) return marginValuesForChild(*this).positiveMarginBefore(); return LayoutUnit(); @@ -765,8 +867,8 @@ LayoutUnit RenderBlockFlow::marginOffsetForSelfCollapsingBlock() void RenderBlockFlow::determineLogicalLeftPositionForChild(RenderBox& child, ApplyLayoutDeltaMode applyDelta) { LayoutUnit startPosition = borderStart() + paddingStart(); - if (style().shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) - startPosition -= verticalScrollbarWidth(); + if (shouldPlaceBlockDirectionScrollbarOnLeft()) + startPosition += (style().isLeftToRightDirection() ? 1 : -1) * verticalScrollbarWidth(); LayoutUnit totalAvailableLogicalWidth = borderAndPaddingLogicalWidth() + availableLogicalWidth(); // Add in our start margin. @@ -775,7 +877,7 @@ void RenderBlockFlow::determineLogicalLeftPositionForChild(RenderBox& child, App // Some objects (e.g., tables, horizontal rules, overflow:auto blocks) avoid floats. They need // to shift over as necessary to dodge any floats that might get in the way. - if (child.avoidsFloats() && containsFloats() && !flowThreadContainingBlock()) + if (child.avoidsFloats() && containsFloats() && !is<RenderNamedFlowThread>(flowThreadContainingBlock())) newPosition += computeStartPositionDeltaForChildAvoidingFloats(child, marginStartForChild(child)); setLogicalLeftForChild(child, style().isLeftToRightDirection() ? newPosition : totalAvailableLogicalWidth - newPosition - logicalWidthForChild(child), applyDelta); @@ -802,10 +904,10 @@ void RenderBlockFlow::adjustFloatingBlock(const MarginInfo& marginInfo) setLogicalHeight(logicalHeight() - marginOffset); } -void RenderBlockFlow::updateStaticInlinePositionForChild(RenderBox& child, LayoutUnit logicalTop) +void RenderBlockFlow::updateStaticInlinePositionForChild(RenderBox& child, LayoutUnit logicalTop, IndentTextOrNot shouldIndentText) { if (child.style().isOriginalDisplayInlineType()) - setStaticInlinePositionForChild(child, logicalTop, startAlignedOffsetForLine(logicalTop, false)); + setStaticInlinePositionForChild(child, logicalTop, startAlignedOffsetForLine(logicalTop, shouldIndentText)); else setStaticInlinePositionForChild(child, logicalTop, startOffsetForContent(logicalTop)); } @@ -829,7 +931,7 @@ RenderBlockFlow::MarginValues RenderBlockFlow::marginValuesForChild(RenderBox& c LayoutUnit beforeMargin = 0; LayoutUnit afterMargin = 0; - RenderBlockFlow* childRenderBlock = child.isRenderBlockFlow() ? toRenderBlockFlow(&child) : nullptr; + RenderBlockFlow* childRenderBlock = is<RenderBlockFlow>(child) ? &downcast<RenderBlockFlow>(child) : nullptr; // If the child has the same directionality as we do, then we can just return its // margins in the same direction. @@ -879,17 +981,45 @@ RenderBlockFlow::MarginValues RenderBlockFlow::marginValuesForChild(RenderBox& c return MarginValues(childBeforePositive, childBeforeNegative, childAfterPositive, childAfterNegative); } +bool RenderBlockFlow::childrenPreventSelfCollapsing() const +{ + if (!childrenInline()) + return RenderBlock::childrenPreventSelfCollapsing(); + + // If the block has inline children, see if we generated any line boxes. If we have any + // line boxes, then we can only be self-collapsing if we have nothing but anonymous inline blocks + // that are also self-collapsing inside us. + if (!hasLines()) + return false; + + if (simpleLineLayout()) + return true; // We have simple line layout lines, so we can't be self-collapsing. + + for (auto* child = firstRootBox(); child; child = child->nextRootBox()) { + if (!child->hasAnonymousInlineBlock() || !child->anonymousInlineBlock()->isSelfCollapsingBlock()) + return true; + } + return false; // We have no line boxes, so we must be self-collapsing. +} + LayoutUnit RenderBlockFlow::collapseMargins(RenderBox& child, MarginInfo& marginInfo) { - bool childDiscardMarginBefore = mustDiscardMarginBeforeForChild(child); - bool childDiscardMarginAfter = mustDiscardMarginAfterForChild(child); - bool childIsSelfCollapsing = child.isSelfCollapsingBlock(); + return collapseMarginsWithChildInfo(&child, child.previousSibling(), marginInfo); +} +LayoutUnit RenderBlockFlow::collapseMarginsWithChildInfo(RenderBox* child, RenderObject* prevSibling, MarginInfo& marginInfo) +{ + bool childDiscardMarginBefore = child ? mustDiscardMarginBeforeForChild(*child) : false; + bool childDiscardMarginAfter = child ? mustDiscardMarginAfterForChild(*child) : false; + bool childIsSelfCollapsing = child ? child->isSelfCollapsingBlock() : false; + bool beforeQuirk = child ? hasMarginBeforeQuirk(*child) : false; + bool afterQuirk = child ? hasMarginAfterQuirk(*child) : false; + // The child discards the before margin when the the after margin has discard in the case of a self collapsing block. childDiscardMarginBefore = childDiscardMarginBefore || (childDiscardMarginAfter && childIsSelfCollapsing); - + // Get the four margin values for the child and cache them. - const MarginValues childMargins = marginValuesForChild(child); + const MarginValues childMargins = child ? marginValuesForChild(*child) : MarginValues(0, 0, 0, 0); // Get our max pos and neg top margins. LayoutUnit posTop = childMargins.positiveMarginBefore(); @@ -902,34 +1032,31 @@ LayoutUnit RenderBlockFlow::collapseMargins(RenderBox& child, MarginInfo& margin negTop = std::max(negTop, childMargins.negativeMarginAfter()); } - // See if the top margin is quirky. We only care if this child has - // margins that will collapse with us. - bool topQuirk = hasMarginBeforeQuirk(child); - if (marginInfo.canCollapseWithMarginBefore()) { if (!childDiscardMarginBefore && !marginInfo.discardMargin()) { // This child is collapsing with the top of the // block. If it has larger margin values, then we need to update // our own maximal values. - if (!document().inQuirksMode() || !marginInfo.quirkContainer() || !topQuirk) + if (!document().inQuirksMode() || !marginInfo.quirkContainer() || !beforeQuirk) setMaxMarginBeforeValues(std::max(posTop, maxPositiveMarginBefore()), std::max(negTop, maxNegativeMarginBefore())); // The minute any of the margins involved isn't a quirk, don't // collapse it away, even if the margin is smaller (www.webreference.com // has an example of this, a <dt> with 0.8em author-specified inside // a <dl> inside a <td>. - if (!marginInfo.determinedMarginBeforeQuirk() && !topQuirk && (posTop - negTop)) { + if (!marginInfo.determinedMarginBeforeQuirk() && !beforeQuirk && (posTop - negTop)) { setHasMarginBeforeQuirk(false); marginInfo.setDeterminedMarginBeforeQuirk(true); } - if (!marginInfo.determinedMarginBeforeQuirk() && topQuirk && !marginBefore()) + if (!marginInfo.determinedMarginBeforeQuirk() && beforeQuirk && !marginBefore()) { // We have no top margin and our top child has a quirky margin. // We will pick up this quirky margin and pass it through. // This deals with the <td><div><p> case. // Don't do this for a block that split two inlines though. You do // still apply margins in this case. setHasMarginBeforeQuirk(true); + } } else // The before margin of the container will also discard all the margins it is collapsing with. setMustDiscardMarginBefore(); @@ -942,18 +1069,18 @@ LayoutUnit RenderBlockFlow::collapseMargins(RenderBox& child, MarginInfo& margin } if (marginInfo.quirkContainer() && marginInfo.atBeforeSideOfBlock() && (posTop - negTop)) - marginInfo.setHasMarginBeforeQuirk(topQuirk); + marginInfo.setHasMarginBeforeQuirk(beforeQuirk); LayoutUnit beforeCollapseLogicalTop = logicalHeight(); LayoutUnit logicalTop = beforeCollapseLogicalTop; LayoutUnit clearanceForSelfCollapsingBlock; - RenderObject* prev = child.previousSibling(); + // If the child's previous sibling is a self-collapsing block that cleared a float then its top border edge has been set at the bottom border edge // of the float. Since we want to collapse the child's top margin with the self-collapsing block's top and bottom margins we need to adjust our parent's height to match the // margin top of the self-collapsing block. If the resulting collapsed margin leaves the child still intruding into the float then we will want to clear it. - if (!marginInfo.canCollapseWithMarginBefore() && prev && prev->isRenderBlockFlow() && toRenderBlockFlow(prev)->isSelfCollapsingBlock()) { - clearanceForSelfCollapsingBlock = toRenderBlockFlow(prev)->marginOffsetForSelfCollapsingBlock(); + if (!marginInfo.canCollapseWithMarginBefore() && is<RenderBlockFlow>(prevSibling) && downcast<RenderBlockFlow>(*prevSibling).isSelfCollapsingBlock()) { + clearanceForSelfCollapsingBlock = downcast<RenderBlockFlow>(*prevSibling).marginOffsetForSelfCollapsingBlock(); setLogicalHeight(logicalHeight() - clearanceForSelfCollapsingBlock); } @@ -981,13 +1108,13 @@ LayoutUnit RenderBlockFlow::collapseMargins(RenderBox& child, MarginInfo& margin logicalTop = logicalHeight() + collapsedBeforePos - collapsedBeforeNeg; } } else { - if (mustSeparateMarginBeforeForChild(child)) { + if (child && mustSeparateMarginBeforeForChild(*child)) { ASSERT(!marginInfo.discardMargin() || (marginInfo.discardMargin() && !marginInfo.margin())); // If we are at the before side of the block and we collapse, ignore the computed margin // and just add the child margin to the container height. This will correctly position // the child inside the container. LayoutUnit separateMargin = !marginInfo.canCollapseWithMarginBefore() ? marginInfo.margin() : LayoutUnit::fromPixel(0); - setLogicalHeight(logicalHeight() + separateMargin + marginBeforeForChild(child)); + setLogicalHeight(logicalHeight() + separateMargin + marginBeforeForChild(*child)); logicalTop = logicalHeight(); } else if (!marginInfo.discardMargin() && (!marginInfo.atBeforeSideOfBlock() || (!marginInfo.canCollapseMarginBeforeWithChildren() @@ -1007,7 +1134,7 @@ LayoutUnit RenderBlockFlow::collapseMargins(RenderBox& child, MarginInfo& margin marginInfo.clearMargin(); if (marginInfo.margin()) - marginInfo.setHasMarginAfterQuirk(hasMarginAfterQuirk(child)); + marginInfo.setHasMarginAfterQuirk(afterQuirk); } // If margins would pull us past the top of the next page, then we need to pull back and pretend like the margins @@ -1020,10 +1147,10 @@ LayoutUnit RenderBlockFlow::collapseMargins(RenderBox& child, MarginInfo& margin setLogicalHeight(logicalHeight() + (logicalTop - oldLogicalTop)); } - if (prev && prev->isRenderBlockFlow() && !prev->isFloatingOrOutOfFlowPositioned()) { + if (is<RenderBlockFlow>(prevSibling) && !prevSibling->isFloatingOrOutOfFlowPositioned()) { // If |child| is a self-collapsing block it may have collapsed into a previous sibling and although it hasn't reduced the height of the parent yet // any floats from the parent will now overhang. - RenderBlockFlow& block = toRenderBlockFlow(*prev); + RenderBlockFlow& block = downcast<RenderBlockFlow>(*prevSibling); LayoutUnit oldLogicalHeight = logicalHeight(); setLogicalHeight(logicalTop); if (block.containsFloats() && !block.avoidsFloats() && (block.logicalTop() + block.lowestFloatLogicalBottom()) > logicalTop) @@ -1034,8 +1161,8 @@ LayoutUnit RenderBlockFlow::collapseMargins(RenderBox& child, MarginInfo& margin // into the margin area of the self-collapsing block then the float it clears is now intruding into |child|. Layout again so that we can look for // floats in the parent that overhang |child|'s new logical top. bool logicalTopIntrudesIntoFloat = clearanceForSelfCollapsingBlock > 0 && logicalTop < beforeCollapseLogicalTop; - if (logicalTopIntrudesIntoFloat && containsFloats() && !child.avoidsFloats() && lowestFloatLogicalBottom() > logicalTop) - child.setNeedsLayout(); + if (child && logicalTopIntrudesIntoFloat && containsFloats() && !child->avoidsFloats() && lowestFloatLogicalBottom() > logicalTop) + child->setNeedsLayout(); } return logicalTop; @@ -1124,10 +1251,10 @@ void RenderBlockFlow::marginBeforeEstimateForChild(RenderBox& child, LayoutUnit& positiveMarginBefore = std::max(positiveMarginBefore, beforeChildMargin); negativeMarginBefore = std::max(negativeMarginBefore, -beforeChildMargin); - if (!child.isRenderBlockFlow()) + if (!is<RenderBlockFlow>(child)) return; - RenderBlockFlow& childBlock = toRenderBlockFlow(child); + RenderBlockFlow& childBlock = downcast<RenderBlockFlow>(child); if (childBlock.childrenInline() || childBlock.isWritingModeRoot()) return; @@ -1147,11 +1274,11 @@ void RenderBlockFlow::marginBeforeEstimateForChild(RenderBox& child, LayoutUnit& // Make sure to update the block margins now for the grandchild box so that we're looking at current values. if (grandchildBox->needsLayout()) { - grandchildBox->computeAndSetBlockDirectionMargins(this); - if (grandchildBox->isRenderBlock()) { - RenderBlock* grandchildBlock = toRenderBlock(grandchildBox); - grandchildBlock->setHasMarginBeforeQuirk(grandchildBox->style().hasMarginBeforeQuirk()); - grandchildBlock->setHasMarginAfterQuirk(grandchildBox->style().hasMarginAfterQuirk()); + grandchildBox->computeAndSetBlockDirectionMargins(*this); + if (is<RenderBlock>(*grandchildBox)) { + RenderBlock& grandchildBlock = downcast<RenderBlock>(*grandchildBox); + grandchildBlock.setHasMarginBeforeQuirk(grandchildBox->style().hasMarginBeforeQuirk()); + grandchildBlock.setHasMarginAfterQuirk(grandchildBox->style().hasMarginAfterQuirk()); } } @@ -1203,8 +1330,8 @@ LayoutUnit RenderBlockFlow::estimateLogicalTopPosition(RenderBox& child, const M // 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); - if (!child.selfNeedsLayout() && child.isRenderBlock()) - logicalTopEstimate += toRenderBlock(child).paginationStrut(); + if (!child.selfNeedsLayout() && is<RenderBlock>(child)) + logicalTopEstimate += downcast<RenderBlock>(child).paginationStrut(); } return logicalTopEstimate; @@ -1243,10 +1370,10 @@ void RenderBlockFlow::handleAfterSideOfBlock(LayoutUnit beforeSide, LayoutUnit a // bottom edge of the float that the child clears. The correct vertical position for the margin-collapsing we want // to perform now is at the child's margin-top - so adjust our height to that position. RenderObject* lastBlock = lastChild(); - if (lastBlock && lastBlock->isRenderBlockFlow() && toRenderBlockFlow(lastBlock)->isSelfCollapsingBlock()) - setLogicalHeight(logicalHeight() - toRenderBlockFlow(lastBlock)->marginOffsetForSelfCollapsingBlock()); + if (is<RenderBlockFlow>(lastBlock) && downcast<RenderBlockFlow>(*lastBlock).isSelfCollapsingBlock()) + setLogicalHeight(logicalHeight() - downcast<RenderBlockFlow>(*lastBlock).marginOffsetForSelfCollapsingBlock()); - // If we can't collapse with children then go ahead and add in the bottom margin. + // If we can't collapse with children then add in the bottom margin. if (!marginInfo.discardMargin() && (!marginInfo.canCollapseWithMarginAfter() && !marginInfo.canCollapseWithMarginBefore() && (!document().inQuirksMode() || !marginInfo.quirkContainer() || !marginInfo.hasMarginAfterQuirk()))) setLogicalHeight(logicalHeight() + marginInfo.margin()); @@ -1332,9 +1459,9 @@ bool RenderBlockFlow::mustDiscardMarginBeforeForChild(const RenderBox& child) co { ASSERT(!child.selfNeedsLayout()); if (!child.isWritingModeRoot()) - return child.isRenderBlockFlow() ? toRenderBlockFlow(child).mustDiscardMarginBefore() : (child.style().marginBeforeCollapse() == MDISCARD); + return is<RenderBlockFlow>(child) ? downcast<RenderBlockFlow>(child).mustDiscardMarginBefore() : (child.style().marginBeforeCollapse() == MDISCARD); if (child.isHorizontalWritingMode() == isHorizontalWritingMode()) - return child.isRenderBlockFlow() ? toRenderBlockFlow(child).mustDiscardMarginAfter() : (child.style().marginAfterCollapse() == MDISCARD); + return is<RenderBlockFlow>(child) ? downcast<RenderBlockFlow>(child).mustDiscardMarginAfter() : (child.style().marginAfterCollapse() == MDISCARD); // FIXME: We return false here because the implementation is not geometrically complete. We have values only for before/after, not start/end. // In case the boxes are perpendicular we assume the property is not specified. @@ -1345,9 +1472,9 @@ bool RenderBlockFlow::mustDiscardMarginAfterForChild(const RenderBox& child) con { ASSERT(!child.selfNeedsLayout()); if (!child.isWritingModeRoot()) - return child.isRenderBlockFlow() ? toRenderBlockFlow(child).mustDiscardMarginAfter() : (child.style().marginAfterCollapse() == MDISCARD); + return is<RenderBlockFlow>(child) ? downcast<RenderBlockFlow>(child).mustDiscardMarginAfter() : (child.style().marginAfterCollapse() == MDISCARD); if (child.isHorizontalWritingMode() == isHorizontalWritingMode()) - return child.isRenderBlockFlow() ? toRenderBlockFlow(child).mustDiscardMarginBefore() : (child.style().marginBeforeCollapse() == MDISCARD); + return is<RenderBlockFlow>(child) ? downcast<RenderBlockFlow>(child).mustDiscardMarginBefore() : (child.style().marginBeforeCollapse() == MDISCARD); // FIXME: See |mustDiscardMarginBeforeForChild| above. return false; @@ -1383,7 +1510,7 @@ static bool inNormalFlow(RenderBox& child) { RenderBlock* curr = child.containingBlock(); while (curr && curr != &child.view()) { - if (curr->hasColumns() || curr->isRenderFlowThread()) + if (curr->isRenderFlowThread()) return true; if (curr->isFloatingOrOutOfFlowPositioned()) return false; @@ -1397,18 +1524,16 @@ LayoutUnit RenderBlockFlow::applyBeforeBreak(RenderBox& child, LayoutUnit logica // FIXME: Add page break checking here when we support printing. RenderFlowThread* flowThread = flowThreadContainingBlock(); bool isInsideMulticolFlowThread = flowThread && !flowThread->isRenderNamedFlowThread(); - bool checkColumnBreaks = isInsideMulticolFlowThread || view().layoutState()->isPaginatingColumns(); + bool checkColumnBreaks = flowThread && flowThread->shouldCheckColumnBreaks(); bool checkPageBreaks = !checkColumnBreaks && view().layoutState()->m_pageLogicalHeight; // FIXME: Once columns can print we have to check this. bool checkRegionBreaks = flowThread && flowThread->isRenderNamedFlowThread(); - bool checkBeforeAlways = (checkColumnBreaks && child.style().columnBreakBefore() == PBALWAYS) - || (checkPageBreaks && child.style().pageBreakBefore() == PBALWAYS) - || (checkRegionBreaks && child.style().regionBreakBefore() == PBALWAYS); + bool checkBeforeAlways = (checkColumnBreaks && child.style().breakBefore() == ColumnBreakBetween) + || (checkPageBreaks && alwaysPageBreak(child.style().breakBefore())) + || (checkRegionBreaks && child.style().breakBefore() == RegionBreakBetween); if (checkBeforeAlways && inNormalFlow(child) && hasNextPage(logicalOffset, IncludePageBoundary)) { if (checkColumnBreaks) { if (isInsideMulticolFlowThread) checkRegionBreaks = true; - else - view().layoutState()->addForcedColumnBreak(&child, logicalOffset); } if (checkRegionBreaks) { LayoutUnit offsetBreakAdjustment = 0; @@ -1425,12 +1550,12 @@ LayoutUnit RenderBlockFlow::applyAfterBreak(RenderBox& child, LayoutUnit logical // FIXME: Add page break checking here when we support printing. RenderFlowThread* flowThread = flowThreadContainingBlock(); bool isInsideMulticolFlowThread = flowThread && !flowThread->isRenderNamedFlowThread(); - bool checkColumnBreaks = isInsideMulticolFlowThread || view().layoutState()->isPaginatingColumns(); + bool checkColumnBreaks = flowThread && flowThread->shouldCheckColumnBreaks(); bool checkPageBreaks = !checkColumnBreaks && view().layoutState()->m_pageLogicalHeight; // FIXME: Once columns can print we have to check this. bool checkRegionBreaks = flowThread && flowThread->isRenderNamedFlowThread(); - bool checkAfterAlways = (checkColumnBreaks && child.style().columnBreakAfter() == PBALWAYS) - || (checkPageBreaks && child.style().pageBreakAfter() == PBALWAYS) - || (checkRegionBreaks && child.style().regionBreakAfter() == PBALWAYS); + bool checkAfterAlways = (checkColumnBreaks && child.style().breakAfter() == ColumnBreakBetween) + || (checkPageBreaks && alwaysPageBreak(child.style().breakAfter())) + || (checkRegionBreaks && child.style().breakAfter() == RegionBreakBetween); if (checkAfterAlways && inNormalFlow(child) && hasNextPage(logicalOffset, IncludePageBoundary)) { LayoutUnit marginOffset = marginInfo.canCollapseWithMarginBefore() ? LayoutUnit() : marginInfo.margin(); @@ -1440,8 +1565,6 @@ LayoutUnit RenderBlockFlow::applyAfterBreak(RenderBox& child, LayoutUnit logical if (checkColumnBreaks) { if (isInsideMulticolFlowThread) checkRegionBreaks = true; - else - view().layoutState()->addForcedColumnBreak(&child, logicalOffset); } if (checkRegionBreaks) { LayoutUnit offsetBreakAdjustment = 0; @@ -1455,7 +1578,7 @@ LayoutUnit RenderBlockFlow::applyAfterBreak(RenderBox& child, LayoutUnit logical LayoutUnit RenderBlockFlow::adjustBlockChildForPagination(LayoutUnit logicalTopAfterClear, LayoutUnit estimateWithoutPagination, RenderBox& child, bool atBeforeSideOfBlock) { - RenderBlock* childRenderBlock = child.isRenderBlock() ? toRenderBlock(&child) : nullptr; + RenderBlock* childRenderBlock = is<RenderBlock>(child) ? &downcast<RenderBlock>(child) : nullptr; if (estimateWithoutPagination != logicalTopAfterClear) { // Our guess prior to pagination movement was wrong. Before we attempt to paginate, let's try again at the new @@ -1464,18 +1587,15 @@ LayoutUnit RenderBlockFlow::adjustBlockChildForPagination(LayoutUnit logicalTopA setLogicalTopForChild(child, logicalTopAfterClear, ApplyLayoutDelta); if (child.shrinkToAvoidFloats()) { - // The child's width depends on the line width. - // When the child shifts to clear an item, its width can - // change (because it has more available line width). - // So go ahead and mark the item as dirty. + // The child's width depends on the line width. When the child shifts to clear an item, its width can + // change (because it has more available line width). So mark the item as dirty. child.setChildNeedsLayout(MarkOnlyThis); } if (childRenderBlock) { if (!child.avoidsFloats() && childRenderBlock->containsFloats()) - toRenderBlockFlow(childRenderBlock)->markAllDescendantsWithFloatsForLayout(); - if (!child.needsLayout()) - child.markForPaginationRelayoutIfNeeded(); + downcast<RenderBlockFlow>(*childRenderBlock).markAllDescendantsWithFloatsForLayout(); + child.markForPaginationRelayoutIfNeeded(); } // Our guess was wrong. Make the child lay itself out again. @@ -1527,20 +1647,20 @@ LayoutUnit RenderBlockFlow::adjustBlockChildForPagination(LayoutUnit logicalTopA result += paginationStrut; } - // Similar to how we apply clearance. Go ahead and boost height() to be the place where we're going to position the child. + // Similar to how we apply clearance. Boost height() to be the place where we're going to position the child. setLogicalHeight(logicalHeight() + (result - oldTop)); // Return the final adjusted logical top. return result; } -static inline LayoutUnit calculateMinimumPageHeight(RenderStyle* renderStyle, RootInlineBox* lastLine, LayoutUnit lineTop, LayoutUnit lineBottom) +static inline LayoutUnit calculateMinimumPageHeight(const RenderStyle& renderStyle, RootInlineBox& lastLine, LayoutUnit lineTop, LayoutUnit lineBottom) { // We may require a certain minimum number of lines per page in order to satisfy // orphans and widows, and that may affect the minimum page height. - unsigned lineCount = std::max<unsigned>(renderStyle->hasAutoOrphans() ? 1 : renderStyle->orphans(), renderStyle->hasAutoWidows() ? 1 : renderStyle->widows()); + unsigned lineCount = std::max<unsigned>(renderStyle.hasAutoOrphans() ? 1 : renderStyle.orphans(), renderStyle.hasAutoWidows() ? 1 : renderStyle.widows()); if (lineCount > 1) { - RootInlineBox* line = lastLine; + RootInlineBox* line = &lastLine; for (unsigned i = 1; i < lineCount && line->prevRootBox(); i++) line = line->prevRootBox(); @@ -1552,8 +1672,31 @@ static inline LayoutUnit calculateMinimumPageHeight(RenderStyle* renderStyle, Ro return lineBottom - lineTop; } -void RenderBlockFlow::adjustLinePositionForPagination(RootInlineBox* lineBox, LayoutUnit& delta, RenderFlowThread* flowThread) +static inline bool needsAppleMailPaginationQuirk(RootInlineBox& lineBox) { + auto& renderer = lineBox.renderer(); + + if (!renderer.settings().appleMailPaginationQuirkEnabled()) + return false; + + if (renderer.element() && renderer.element()->idForStyleResolution() == "messageContentContainer") + return true; + + return false; +} + +static void clearShouldBreakAtLineToAvoidWidowIfNeeded(RenderBlockFlow& blockFlow) +{ + if (!blockFlow.shouldBreakAtLineToAvoidWidow()) + return; + blockFlow.clearShouldBreakAtLineToAvoidWidow(); + blockFlow.setDidBreakAtLineToAvoidWidow(); +} + +void RenderBlockFlow::adjustLinePositionForPagination(RootInlineBox* lineBox, LayoutUnit& delta, bool& overflowsRegion, RenderFlowThread* flowThread) +{ + // FIXME: Ignore anonymous inline blocks. Handle the delta already having been set because of + // collapsing margins from a previous anonymous inline block. // FIXME: For now we paginate using line overflow. This ensures that lines don't overlap at all when we // put a strut between them for pagination purposes. However, this really isn't the desired rendering, since // the line on the top of the next page will appear too far down relative to the same kind of line at the top @@ -1573,11 +1716,12 @@ void RenderBlockFlow::adjustLinePositionForPagination(RootInlineBox* lineBox, La // FIXME: Another problem with simply moving lines is that the available line width may change (because of floats). // Technically if the location we move the line to has a different line width than our old position, then we need to dirty the // line and all following lines. + overflowsRegion = false; LayoutRect logicalVisualOverflow = lineBox->logicalVisualOverflowRect(lineBox->lineTop(), lineBox->lineBottom()); LayoutUnit logicalOffset = std::min(lineBox->lineTopWithLeading(), logicalVisualOverflow.y()); LayoutUnit logicalBottom = std::max(lineBox->lineBottomWithLeading(), logicalVisualOverflow.maxY()); LayoutUnit lineHeight = logicalBottom - logicalOffset; - updateMinimumPageHeight(logicalOffset, calculateMinimumPageHeight(&style(), lineBox, logicalOffset, logicalBottom)); + updateMinimumPageHeight(logicalOffset, calculateMinimumPageHeight(style(), *lineBox, logicalOffset, logicalBottom)); logicalOffset += delta; lineBox->setPaginationStrut(0); lineBox->setIsFirstAfterPageBreak(false); @@ -1585,19 +1729,38 @@ void RenderBlockFlow::adjustLinePositionForPagination(RootInlineBox* lineBox, La bool hasUniformPageLogicalHeight = !flowThread || flowThread->regionsHaveUniformLogicalHeight(); // If lineHeight is greater than pageLogicalHeight, but logicalVisualOverflow.height() still fits, we are // still going to add a strut, so that the visible overflow fits on a single page. - if (!pageLogicalHeight || (hasUniformPageLogicalHeight && logicalVisualOverflow.height() > pageLogicalHeight) - || !hasNextPage(logicalOffset)) + if (!pageLogicalHeight || !hasNextPage(logicalOffset)) { // FIXME: In case the line aligns with the top of the page (or it's slightly shifted downwards) it will not be marked as the first line in the page. // From here, the fix is not straightforward because it's not easy to always determine when the current line is the first in the page. return; + } + + if (hasUniformPageLogicalHeight && logicalVisualOverflow.height() > pageLogicalHeight) { + // We are so tall that we are bigger than a page. Before we give up and just leave the line where it is, try drilling into the + // line and computing a new height that excludes anything we consider "blank space". We will discard margins, descent, and even overflow. If we are + // able to fit with the blank space and overflow excluded, we will give the line its own page with the highest non-blank element being aligned with the + // top of the page. + // FIXME: We are still honoring gigantic margins, which does leave open the possibility of blank pages caused by this heuristic. It remains to be seen whether or not + // this will be a real-world issue. For now we don't try to deal with this problem. + logicalOffset = intMaxForLayoutUnit; + logicalBottom = intMinForLayoutUnit; + lineBox->computeReplacedAndTextLineTopAndBottom(logicalOffset, logicalBottom); + lineHeight = logicalBottom - logicalOffset; + if (logicalOffset == intMaxForLayoutUnit || lineHeight > pageLogicalHeight) { + // Give up. We're genuinely too big even after excluding blank space and overflow. + clearShouldBreakAtLineToAvoidWidowIfNeeded(*this); + return; + } + pageLogicalHeight = pageLogicalHeightForOffset(logicalOffset); + } + LayoutUnit remainingLogicalHeight = pageRemainingLogicalHeightForOffset(logicalOffset, ExcludePageBoundary); + overflowsRegion = (lineHeight > remainingLogicalHeight); int lineIndex = lineCount(lineBox); if (remainingLogicalHeight < lineHeight || (shouldBreakAtLineToAvoidWidow() && lineBreakToAvoidWidow() == lineIndex)) { - if (shouldBreakAtLineToAvoidWidow() && lineBreakToAvoidWidow() == lineIndex) { - clearShouldBreakAtLineToAvoidWidow(); - setDidBreakAtLineToAvoidWidow(); - } + if (lineBreakToAvoidWidow() == lineIndex) + clearShouldBreakAtLineToAvoidWidowIfNeeded(*this); // If we have a non-uniform page height, then we have to shift further possibly. if (!hasUniformPageLogicalHeight && !pushToNextPageWithMinimumLogicalHeight(remainingLogicalHeight, logicalOffset, lineHeight)) return; @@ -1605,13 +1768,20 @@ void RenderBlockFlow::adjustLinePositionForPagination(RootInlineBox* lineBox, La // Split the top margin in order to avoid splitting the visible part of the line. remainingLogicalHeight -= std::min(lineHeight - pageLogicalHeight, std::max<LayoutUnit>(0, logicalVisualOverflow.y() - lineBox->lineTopWithLeading())); } + LayoutUnit remainingLogicalHeightAtNewOffset = pageRemainingLogicalHeightForOffset(logicalOffset + remainingLogicalHeight, ExcludePageBoundary); + overflowsRegion = (lineHeight > remainingLogicalHeightAtNewOffset); LayoutUnit totalLogicalHeight = lineHeight + std::max<LayoutUnit>(0, logicalOffset); LayoutUnit pageLogicalHeightAtNewOffset = hasUniformPageLogicalHeight ? pageLogicalHeight : pageLogicalHeightForOffset(logicalOffset + remainingLogicalHeight); setPageBreak(logicalOffset, lineHeight - remainingLogicalHeight); if (((lineBox == firstRootBox() && totalLogicalHeight < pageLogicalHeightAtNewOffset) || (!style().hasAutoOrphans() && style().orphans() >= lineIndex)) - && !isOutOfFlowPositioned() && !isTableCell()) - setPaginationStrut(remainingLogicalHeight + std::max<LayoutUnit>(0, logicalOffset)); - else { + && !isOutOfFlowPositioned() && !isTableCell()) { + auto firstRootBox = this->firstRootBox(); + auto firstRootBoxOverflowRect = firstRootBox->logicalVisualOverflowRect(firstRootBox->lineTop(), firstRootBox->lineBottom()); + auto firstLineUpperOverhang = std::max(-firstRootBoxOverflowRect.y(), LayoutUnit()); + if (needsAppleMailPaginationQuirk(*lineBox)) + return; + setPaginationStrut(remainingLogicalHeight + logicalOffset + firstLineUpperOverhang); + } else { delta += remainingLogicalHeight; lineBox->setPaginationStrut(remainingLogicalHeight); lineBox->setIsFirstAfterPageBreak(true); @@ -1682,30 +1852,23 @@ bool RenderBlockFlow::hasNextPage(LayoutUnit logicalOffset, PageBoundaryRule pag RenderRegion* region = flowThread->regionAtBlockOffset(this, pageOffset, true); if (!region) return false; + if (region->isLastRegion()) return region->isRenderRegionSet() || region->style().regionFragment() == BreakRegionFragment || (pageBoundaryRule == IncludePageBoundary && pageOffset == region->logicalTopForFlowThreadContent()); - RenderRegion* startRegion = 0; - RenderRegion* endRegion = 0; + RenderRegion* startRegion = nullptr; + RenderRegion* endRegion = nullptr; flowThread->getRegionRangeForBox(this, startRegion, endRegion); - - if (region == endRegion) - return false; - return true; + return (endRegion && region != endRegion); } LayoutUnit RenderBlockFlow::adjustForUnsplittableChild(RenderBox& child, LayoutUnit logicalOffset, bool includeMargins) { - bool checkColumnBreaks = view().layoutState()->isPaginatingColumns(); - bool checkPageBreaks = !checkColumnBreaks && view().layoutState()->m_pageLogicalHeight; - RenderFlowThread* flowThread = flowThreadContainingBlock(); - bool checkRegionBreaks = flowThread && flowThread->isRenderNamedFlowThread(); - bool isUnsplittable = child.isUnsplittableForPagination() || (checkColumnBreaks && child.style().columnBreakInside() == PBAVOID) - || (checkPageBreaks && child.style().pageBreakInside() == PBAVOID) - || (checkRegionBreaks && child.style().regionBreakInside() == PBAVOID); - if (!isUnsplittable) + if (!childBoxIsUnsplittableForFragmentation(child)) return logicalOffset; + + RenderFlowThread* flowThread = flowThreadContainingBlock(); LayoutUnit childLogicalHeight = logicalHeightForChild(child) + (includeMargins ? marginBeforeForChild(child) + marginAfterForChild(child) : LayoutUnit()); LayoutUnit pageLogicalHeight = pageLogicalHeightForOffset(logicalOffset); bool hasUniformPageLogicalHeight = !flowThread || flowThread->regionsHaveUniformLogicalHeight(); @@ -1747,8 +1910,6 @@ void RenderBlockFlow::updateMinimumPageHeight(LayoutUnit offset, LayoutUnit minH { if (RenderFlowThread* flowThread = flowThreadContainingBlock()) flowThread->updateMinimumPageHeight(this, offsetFromLogicalTopOfFirstPage() + offset, minHeight); - else if (ColumnInfo* colInfo = view().layoutState()->m_columnInfo) - colInfo->updateMinimumColumnHeight(minHeight); } LayoutUnit RenderBlockFlow::nextPageLogicalTop(LayoutUnit logicalOffset, PageBoundaryRule pageBoundaryRule) const @@ -1766,25 +1927,34 @@ LayoutUnit RenderBlockFlow::nextPageLogicalTop(LayoutUnit logicalOffset, PageBou LayoutUnit RenderBlockFlow::pageLogicalTopForOffset(LayoutUnit offset) const { + // Unsplittable objects clear out the pageLogicalHeight in the layout state as a way of signaling that no + // pagination should occur. Therefore we have to check this first and bail if the value has been set to 0. + LayoutUnit pageLogicalHeight = view().layoutState()->m_pageLogicalHeight; + if (!pageLogicalHeight) + return 0; + LayoutUnit firstPageLogicalTop = isHorizontalWritingMode() ? view().layoutState()->m_pageOffset.height() : view().layoutState()->m_pageOffset.width(); LayoutUnit blockLogicalTop = isHorizontalWritingMode() ? view().layoutState()->m_layoutOffset.height() : view().layoutState()->m_layoutOffset.width(); LayoutUnit cumulativeOffset = offset + blockLogicalTop; RenderFlowThread* flowThread = flowThreadContainingBlock(); - if (!flowThread) { - LayoutUnit pageLogicalHeight = view().layoutState()->pageLogicalHeight(); - if (!pageLogicalHeight) - return 0; + if (!flowThread) return cumulativeOffset - roundToInt(cumulativeOffset - firstPageLogicalTop) % roundToInt(pageLogicalHeight); - } - return flowThread->pageLogicalTopForOffset(cumulativeOffset); + return firstPageLogicalTop + flowThread->pageLogicalTopForOffset(cumulativeOffset - firstPageLogicalTop); } LayoutUnit RenderBlockFlow::pageLogicalHeightForOffset(LayoutUnit offset) const { + // Unsplittable objects clear out the pageLogicalHeight in the layout state as a way of signaling that no + // pagination should occur. Therefore we have to check this first and bail if the value has been set to 0. + LayoutUnit pageLogicalHeight = view().layoutState()->m_pageLogicalHeight; + if (!pageLogicalHeight) + return 0; + + // Now check for a flow thread. RenderFlowThread* flowThread = flowThreadContainingBlock(); if (!flowThread) - return view().layoutState()->m_pageLogicalHeight; + return pageLogicalHeight; return flowThread->pageLogicalHeightForOffset(offset + offsetFromLogicalTopOfFirstPage()); } @@ -1807,6 +1977,35 @@ LayoutUnit RenderBlockFlow::pageRemainingLogicalHeightForOffset(LayoutUnit offse return flowThread->pageRemainingLogicalHeightForOffset(offset, pageBoundaryRule); } +LayoutUnit RenderBlockFlow::logicalHeightForChildForFragmentation(const RenderBox& child) const +{ + // This method is required because regions do not fragment monolithic elements but instead + // they let them overflow the region they flow in. This behaviour is different from the + // multicol/printing implementations, which have not yet been updated to correctly handle + // monolithic elements. + // As a result, for the moment, this method will only be used for regions, the multicol and + // printing implementations will stick to the existing behaviour until their fragmentation + // implementation is updated to match the regions implementation. + if (!flowThreadContainingBlock() || !flowThreadContainingBlock()->isRenderNamedFlowThread()) + return logicalHeightForChild(child); + + // For unsplittable elements, this method will just return the height of the element that + // fits into the current region, without the height of the part that overflows the region. + // This is done for all regions, except the last one because in that case, the logical + // height of the flow thread needs to also + if (!childBoxIsUnsplittableForFragmentation(child) || !pageLogicalHeightForOffset(logicalTopForChild(child))) + return logicalHeightForChild(child); + + // If we're on the last page this block fragments to, the logical height of the flow thread must include + // the entire unsplittable child because any following children will not be moved to the next page + // so they will need to be laid out below the current unsplittable child. + LayoutUnit childLogicalTop = logicalTopForChild(child); + if (!hasNextPage(childLogicalTop)) + return logicalHeightForChild(child); + + LayoutUnit remainingLogicalHeight = pageRemainingLogicalHeightForOffset(childLogicalTop, ExcludePageBoundary); + return std::min(child.logicalHeight(), remainingLogicalHeight); +} void RenderBlockFlow::layoutLineGridBox() { @@ -1824,7 +2023,7 @@ void RenderBlockFlow::layoutLineGridBox() VerticalPositionCache verticalPositionCache; lineGridBox->alignBoxesInBlockDirection(logicalHeight(), textBoxDataMap, verticalPositionCache); - setLineGridBox(std::move(lineGridBox)); + setLineGridBox(WTFMove(lineGridBox)); // FIXME: If any of the characteristics of the box change compared to the old one, then we need to do a deep dirtying // (similar to what happens when the page height changes). Ideally, though, we only do this if someone is actually snapping @@ -1866,17 +2065,28 @@ void RenderBlockFlow::styleDidChange(StyleDifference diff, const RenderStyle* ol parentBlock->markAllDescendantsWithFloatsForLayout(); parentBlock->markSiblingsWithFloatsForLayout(); } + // Fresh floats need to be reparented if they actually belong to the previous anonymous block. + // It copies the logic of RenderBlock::addChildIgnoringContinuation + if (noLongerAffectsParentBlock() && style().isFloating() && previousSibling() && previousSibling()->isAnonymousBlock()) + downcast<RenderBoxModelObject>(*parent()).moveChildTo(&downcast<RenderBoxModelObject>(*previousSibling()), this); if (auto fragment = renderNamedFlowFragment()) fragment->setStyle(RenderNamedFlowFragment::createStyle(style())); - if (diff >= StyleDifferenceRepaint) - invalidateLineLayoutPath(); - - if (multiColumnFlowThread()) { - for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) - child->setStyle(RenderStyle::createAnonymousStyleWithDisplay(&style(), BLOCK)); + if (diff >= StyleDifferenceRepaint) { + // FIXME: This could use a cheaper style-only test instead of SimpleLineLayout::canUseFor. + if (selfNeedsLayout() || !m_simpleLineLayout || !SimpleLineLayout::canUseFor(*this)) + invalidateLineLayoutPath(); } + + if (multiColumnFlowThread()) + updateStylesForColumnChildren(); +} + +void RenderBlockFlow::updateStylesForColumnChildren() +{ + for (auto* child = firstChildBox(); child && (child->isInFlowRenderFlowThread() || child->isRenderMultiColumnSet()); child = child->nextSiblingBox()) + child->setStyle(RenderStyle::createAnonymousStyleWithDisplay(style(), BLOCK)); } void RenderBlockFlow::styleWillChange(StyleDifference diff, const RenderStyle& newStyle) @@ -1884,9 +2094,14 @@ void RenderBlockFlow::styleWillChange(StyleDifference diff, const RenderStyle& n const RenderStyle* oldStyle = hasInitializedStyle() ? &style() : nullptr; s_canPropagateFloatIntoSibling = oldStyle ? !isFloatingOrOutOfFlowPositioned() && !avoidsFloats() : false; - if (oldStyle && parent() && diff == StyleDifferenceLayout && oldStyle->position() != newStyle.position()) { - if (containsFloats() && !isFloating() && !isOutOfFlowPositioned() && newStyle.hasOutOfFlowPosition()) - markAllDescendantsWithFloatsForLayout(); + if (oldStyle) { + EPosition oldPosition = oldStyle->position(); + EPosition newPosition = newStyle.position(); + + if (parent() && diff == StyleDifferenceLayout && oldPosition != newPosition) { + if (containsFloats() && !isFloating() && !isOutOfFlowPositioned() && newStyle.hasOutOfFlowPosition()) + markAllDescendantsWithFloatsForLayout(); + } } RenderBlock::styleWillChange(diff, newStyle); @@ -1906,11 +2121,8 @@ void RenderBlockFlow::deleteLines() RenderBlock::deleteLines(); } -void RenderBlockFlow::moveAllChildrenIncludingFloatsTo(RenderBlock* toBlock, bool fullRemoveInsert) +void RenderBlockFlow::addFloatsToNewParent(RenderBlockFlow& toBlockFlow) const { - RenderBlockFlow* toBlockFlow = toRenderBlockFlow(toBlock); - moveAllChildrenTo(toBlockFlow, fullRemoveInsert); - // When a portion of the render tree is being detached, anonymous blocks // will be combined as their children are deleted. In this process, the // anonymous block later in the tree is merged into the one preceeding it. @@ -1928,25 +2140,26 @@ void RenderBlockFlow::moveAllChildrenIncludingFloatsTo(RenderBlock* toBlock, boo // all be wrong, but since toBlockFlow is already marked for layout, this // will get fixed before anything gets displayed. // See bug https://bugs.webkit.org/show_bug.cgi?id=115566 - if (m_floatingObjects) { - if (!toBlockFlow->m_floatingObjects) - toBlockFlow->createFloatingObjects(); - - const FloatingObjectSet& fromFloatingObjectSet = m_floatingObjects->set(); - auto end = fromFloatingObjectSet.end(); - - for (auto it = fromFloatingObjectSet.begin(); it != end; ++it) { - FloatingObject* floatingObject = it->get(); + if (!m_floatingObjects) + return; - // Don't insert the object again if it's already in the list - if (toBlockFlow->containsFloat(floatingObject->renderer())) - continue; + if (!toBlockFlow.m_floatingObjects) + toBlockFlow.createFloatingObjects(); - toBlockFlow->m_floatingObjects->add(floatingObject->unsafeClone()); - } + for (auto& floatingObject : m_floatingObjects->set()) { + if (toBlockFlow.containsFloat(floatingObject->renderer())) + continue; + toBlockFlow.m_floatingObjects->add(floatingObject->cloneForNewParent()); } } +void RenderBlockFlow::moveAllChildrenIncludingFloatsTo(RenderBlock& toBlock, bool fullRemoveInsert) +{ + auto& toBlockFlow = downcast<RenderBlockFlow>(toBlock); + moveAllChildrenTo(&toBlockFlow, fullRemoveInsert); + addFloatsToNewParent(toBlockFlow); +} + void RenderBlockFlow::addOverflowFromFloats() { if (!m_floatingObjects) @@ -1955,9 +2168,9 @@ void RenderBlockFlow::addOverflowFromFloats() const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto end = floatingObjectSet.end(); for (auto it = floatingObjectSet.begin(); it != end; ++it) { - FloatingObject* r = it->get(); - if (r->isDescendant()) - addOverflowFromChild(&r->renderer(), IntSize(xPositionForFloatIncludingMargin(r), yPositionForFloatIncludingMargin(r))); + const auto& floatingObject = *it->get(); + if (floatingObject.isDescendant()) + addOverflowFromChild(&floatingObject.renderer(), IntSize(xPositionForFloatIncludingMargin(floatingObject), yPositionForFloatIncludingMargin(floatingObject))); } } @@ -1965,7 +2178,7 @@ void RenderBlockFlow::computeOverflow(LayoutUnit oldClientAfterEdge, bool recomp { RenderBlock::computeOverflow(oldClientAfterEdge, recomputeFloats); - if (!hasColumns() && (recomputeFloats || isRoot() || expandsToEncloseOverhangingFloats() || hasSelfPaintingLayer())) + if (!multiColumnFlowThread() && (recomputeFloats || createsNewFormattingContext() || hasSelfPaintingLayer())) addOverflowFromFloats(); } @@ -1978,23 +2191,38 @@ void RenderBlockFlow::repaintOverhangingFloats(bool paintAllDescendants) // FIXME: Avoid disabling LayoutState. At the very least, don't disable it for floats originating // in this block. Better yet would be to push extra state for the containers of other floats. - LayoutStateDisabler layoutStateDisabler(&view()); + LayoutStateDisabler layoutStateDisabler(view()); const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto end = floatingObjectSet.end(); for (auto it = floatingObjectSet.begin(); it != end; ++it) { - FloatingObject* floatingObject = it->get(); + const auto& floatingObject = *it->get(); // Only repaint the object if it is overhanging, is not in its own layer, and // is our responsibility to paint (m_shouldPaint is set). When paintAllDescendants is true, the latter // condition is replaced with being a descendant of us. + auto& renderer = floatingObject.renderer(); if (logicalBottomForFloat(floatingObject) > logicalHeight() - && !floatingObject->renderer().hasSelfPaintingLayer() - && (floatingObject->shouldPaint() || (paintAllDescendants && floatingObject->renderer().isDescendantOf(this)))) { - floatingObject->renderer().repaint(); - floatingObject->renderer().repaintOverhangingFloats(false); + && !renderer.hasSelfPaintingLayer() + && (floatingObject.shouldPaint() || (paintAllDescendants && renderer.isDescendantOf(this)))) { + renderer.repaint(); + renderer.repaintOverhangingFloats(false); } } } +void RenderBlockFlow::paintColumnRules(PaintInfo& paintInfo, const LayoutPoint& point) +{ + RenderBlock::paintColumnRules(paintInfo, point); + + if (!multiColumnFlowThread() || paintInfo.context().paintingDisabled()) + return; + + // Iterate over our children and paint the column rules as needed. + for (auto& columnSet : childrenOfType<RenderMultiColumnSet>(*this)) { + LayoutPoint childPoint = columnSet.location() + flipForWritingModeForChild(&columnSet, point); + columnSet.paintColumnRules(paintInfo, childPoint); + } +} + void RenderBlockFlow::paintFloats(PaintInfo& paintInfo, const LayoutPoint& paintOffset, bool preservePhase) { if (!m_floatingObjects) @@ -2003,23 +2231,26 @@ void RenderBlockFlow::paintFloats(PaintInfo& paintInfo, const LayoutPoint& paint const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto end = floatingObjectSet.end(); for (auto it = floatingObjectSet.begin(); it != end; ++it) { - FloatingObject* r = it->get(); + const auto& floatingObject = *it->get(); + auto& renderer = floatingObject.renderer(); // Only paint the object if our m_shouldPaint flag is set. - if (r->shouldPaint() && !r->renderer().hasSelfPaintingLayer()) { + if (floatingObject.shouldPaint() && !renderer.hasSelfPaintingLayer()) { PaintInfo currentPaintInfo(paintInfo); currentPaintInfo.phase = preservePhase ? paintInfo.phase : PaintPhaseBlockBackground; // FIXME: LayoutPoint version of xPositionForFloatIncludingMargin would make this much cleaner. - LayoutPoint childPoint = flipFloatForWritingModeForChild(r, LayoutPoint(paintOffset.x() + xPositionForFloatIncludingMargin(r) - r->renderer().x(), paintOffset.y() + yPositionForFloatIncludingMargin(r) - r->renderer().y())); - r->renderer().paint(currentPaintInfo, childPoint); + LayoutPoint childPoint = flipFloatForWritingModeForChild(floatingObject, + LayoutPoint(paintOffset.x() + xPositionForFloatIncludingMargin(floatingObject) - renderer.x(), + paintOffset.y() + yPositionForFloatIncludingMargin(floatingObject) - renderer.y())); + renderer.paint(currentPaintInfo, childPoint); if (!preservePhase) { currentPaintInfo.phase = PaintPhaseChildBlockBackgrounds; - r->renderer().paint(currentPaintInfo, childPoint); + renderer.paint(currentPaintInfo, childPoint); currentPaintInfo.phase = PaintPhaseFloat; - r->renderer().paint(currentPaintInfo, childPoint); + renderer.paint(currentPaintInfo, childPoint); currentPaintInfo.phase = PaintPhaseForeground; - r->renderer().paint(currentPaintInfo, childPoint); + renderer.paint(currentPaintInfo, childPoint); currentPaintInfo.phase = PaintPhaseOutline; - r->renderer().paint(currentPaintInfo, childPoint); + renderer.paint(currentPaintInfo, childPoint); } } } @@ -2031,20 +2262,20 @@ void RenderBlockFlow::clipOutFloatingObjects(RenderBlock& rootBlock, const Paint const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto end = floatingObjectSet.end(); for (auto it = floatingObjectSet.begin(); it != end; ++it) { - FloatingObject* floatingObject = it->get(); + const auto& floatingObject = *it->get(); LayoutRect floatBox(offsetFromRootBlock.width() + xPositionForFloatIncludingMargin(floatingObject), offsetFromRootBlock.height() + yPositionForFloatIncludingMargin(floatingObject), - floatingObject->renderer().width(), floatingObject->renderer().height()); + floatingObject.renderer().width(), floatingObject.renderer().height()); rootBlock.flipForWritingMode(floatBox); floatBox.move(rootBlockPhysicalPosition.x(), rootBlockPhysicalPosition.y()); - paintInfo->context->clipOut(pixelSnappedIntRect(floatBox)); + paintInfo->context().clipOut(snappedIntRect(floatBox)); } } } void RenderBlockFlow::createFloatingObjects() { - m_floatingObjects = adoptPtr(new FloatingObjects(*this)); + m_floatingObjects = std::make_unique<FloatingObjects>(*this); } void RenderBlockFlow::removeFloatingObjects() @@ -2052,6 +2283,8 @@ void RenderBlockFlow::removeFloatingObjects() if (!m_floatingObjects) return; + markSiblingsWithFloatsForLayout(); + m_floatingObjects->clear(); } @@ -2074,23 +2307,25 @@ FloatingObject* RenderBlockFlow::insertFloatingObject(RenderBox& floatBox) std::unique_ptr<FloatingObject> floatingObject = FloatingObject::create(floatBox); - // Our location is irrelevant if we're unsplittable or no pagination is in effect. - // Just go ahead and lay out the float. + // Our location is irrelevant if we're unsplittable or no pagination is in effect. Just lay out the float. bool isChildRenderBlock = floatBox.isRenderBlock(); if (isChildRenderBlock && !floatBox.needsLayout() && view().layoutState()->pageLogicalHeightChanged()) floatBox.setChildNeedsLayout(MarkOnlyThis); bool needsBlockDirectionLocationSetBeforeLayout = isChildRenderBlock && view().layoutState()->needsBlockDirectionLocationSetBeforeLayout(); - if (!needsBlockDirectionLocationSetBeforeLayout || isWritingModeRoot()) // We are unsplittable if we're a block flow root. + if (!needsBlockDirectionLocationSetBeforeLayout || isWritingModeRoot()) { + // We are unsplittable if we're a block flow root. floatBox.layoutIfNeeded(); + floatingObject->setShouldPaint(!floatBox.hasSelfPaintingLayer()); + } else { floatBox.updateLogicalWidth(); - floatBox.computeAndSetBlockDirectionMargins(this); + floatBox.computeAndSetBlockDirectionMargins(*this); } - setLogicalWidthForFloat(floatingObject.get(), logicalWidthForChild(floatBox) + marginStartForChild(floatBox) + marginEndForChild(floatBox)); + setLogicalWidthForFloat(*floatingObject, logicalWidthForChild(floatBox) + marginStartForChild(floatBox) + marginEndForChild(floatBox)); - return m_floatingObjects->add(std::move(floatingObject)); + return m_floatingObjects->add(WTFMove(floatingObject)); } void RenderBlockFlow::removeFloatingObject(RenderBox& floatBox) @@ -2099,7 +2334,7 @@ void RenderBlockFlow::removeFloatingObject(RenderBox& floatBox) const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto it = floatingObjectSet.find<RenderBox&, FloatingObjectHashTranslator>(floatBox); if (it != floatingObjectSet.end()) { - FloatingObject* floatingObject = it->get(); + auto& floatingObject = *it->get(); if (childrenInline()) { LayoutUnit logicalTop = logicalTopForFloat(floatingObject); LayoutUnit logicalBottom = logicalBottomForFloat(floatingObject); @@ -2113,19 +2348,19 @@ void RenderBlockFlow::removeFloatingObject(RenderBox& floatBox) // accomplished by pretending they have a height of 1. logicalBottom = std::max(logicalBottom, logicalTop + 1); } - if (floatingObject->originatingLine()) { - floatingObject->originatingLine()->removeFloat(floatBox); + if (floatingObject.originatingLine()) { + floatingObject.originatingLine()->removeFloat(floatBox); if (!selfNeedsLayout()) { - ASSERT(&floatingObject->originatingLine()->renderer() == this); - floatingObject->originatingLine()->markDirty(); + ASSERT(&floatingObject.originatingLine()->renderer() == this); + floatingObject.originatingLine()->markDirty(); } #if !ASSERT_DISABLED - floatingObject->setOriginatingLine(0); + floatingObject.setOriginatingLine(0); #endif } markLinesDirtyInBlockRange(0, logicalBottom); } - m_floatingObjects->remove(floatingObject); + m_floatingObjects->remove(&floatingObject); } } } @@ -2137,7 +2372,7 @@ void RenderBlockFlow::removeFloatingObjectsBelow(FloatingObject* lastFloat, int const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); FloatingObject* curr = floatingObjectSet.last().get(); - while (curr != lastFloat && (!curr->isPlaced() || logicalTopForFloat(curr) >= logicalOffset)) { + while (curr != lastFloat && (!curr->isPlaced() || logicalTopForFloat(*curr) >= logicalOffset)) { m_floatingObjects->remove(curr); if (floatingObjectSet.isEmpty()) break; @@ -2161,42 +2396,27 @@ LayoutUnit RenderBlockFlow::logicalRightOffsetForPositioningFloat(LayoutUnit log return adjustLogicalRightOffsetForLine(offset, applyTextIndent); } -LayoutPoint RenderBlockFlow::computeLogicalLocationForFloat(const FloatingObject* floatingObject, LayoutUnit logicalTopOffset) const +LayoutPoint RenderBlockFlow::computeLogicalLocationForFloat(const FloatingObject& floatingObject, LayoutUnit logicalTopOffset) { - RenderBox& childBox = floatingObject->renderer(); + auto& childBox = floatingObject.renderer(); LayoutUnit logicalLeftOffset = logicalLeftOffsetForContent(logicalTopOffset); // Constant part of left offset. - LayoutUnit logicalRightOffset; // Constant part of right offset. -#if ENABLE(CSS_SHAPES) - // FIXME Bug 102948: This only works for shape outside directly set on this block. - ShapeInsideInfo* shapeInsideInfo = this->layoutShapeInsideInfo(); - // FIXME: Implement behavior for right floats. - if (shapeInsideInfo) { - LayoutSize floatLogicalSize = logicalSizeForFloat(floatingObject); - // floatingObject's logicalSize doesn't contain the actual height at this point, so we need to calculate it - floatLogicalSize.setHeight(logicalHeightForChild(childBox) + marginBeforeForChild(childBox) + marginAfterForChild(childBox)); - - // FIXME: If the float doesn't fit in the shape we should push it under the content box - logicalTopOffset = shapeInsideInfo->computeFirstFitPositionForFloat(floatLogicalSize); - if (logicalHeight() > logicalTopOffset) - logicalTopOffset = logicalHeight(); - - SegmentList segments = shapeInsideInfo->computeSegmentsForLine(logicalTopOffset, floatLogicalSize.height()); - // FIXME Bug 102949: Add support for shapes with multiple segments. - if (segments.size() == 1) { - // The segment offsets are relative to the content box. - logicalRightOffset = logicalLeftOffset + segments[0].logicalRight; - logicalLeftOffset += segments[0].logicalLeft; - } - } else -#endif - logicalRightOffset = logicalRightOffsetForContent(logicalTopOffset); + LayoutUnit logicalRightOffset = logicalRightOffsetForContent(logicalTopOffset); // Constant part of right offset. LayoutUnit floatLogicalWidth = std::min(logicalWidthForFloat(floatingObject), logicalRightOffset - logicalLeftOffset); // The width we look for. LayoutUnit floatLogicalLeft; bool insideFlowThread = flowThreadContainingBlock(); - + bool isInitialLetter = childBox.style().styleType() == FIRST_LETTER && childBox.style().initialLetterDrop() > 0; + + if (isInitialLetter) { + int letterClearance = lowestInitialLetterLogicalBottom() - logicalTopOffset; + if (letterClearance > 0) { + logicalTopOffset += letterClearance; + setLogicalHeight(logicalHeight() + letterClearance); + } + } + if (childBox.style().floating() == LeftFloat) { LayoutUnit heightRemainingLeft = 1; LayoutUnit heightRemainingRight = 1; @@ -2232,6 +2452,34 @@ LayoutPoint RenderBlockFlow::computeLogicalLocationForFloat(const FloatingObject floatLogicalLeft -= logicalWidthForFloat(floatingObject); } + if (isInitialLetter) { + const RenderStyle& style = firstLineStyle(); + const FontMetrics& fontMetrics = style.fontMetrics(); + if (fontMetrics.hasCapHeight()) { + LayoutUnit heightOfLine = lineHeight(true, isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); + LayoutUnit beforeMarginBorderPadding = childBox.borderAndPaddingBefore() + childBox.marginBefore(); + + // Make an adjustment to align with the cap height of a theoretical block line. + LayoutUnit adjustment = fontMetrics.ascent() + (heightOfLine - fontMetrics.height()) / 2 - fontMetrics.capHeight() - beforeMarginBorderPadding; + logicalTopOffset += adjustment; + + // For sunken and raised caps, we have to make some adjustments. Test if we're sunken or raised (dropHeightDelta will be + // positive for raised and negative for sunken). + int dropHeightDelta = childBox.style().initialLetterHeight() - childBox.style().initialLetterDrop(); + + // If we're sunken, the float needs to shift down but lines still need to avoid it. In order to do that we increase the float's margin. + if (dropHeightDelta < 0) { + LayoutUnit marginTopIncrease = -dropHeightDelta * heightOfLine; + childBox.setMarginBefore(childBox.marginTop() + marginTopIncrease); + } + + // If we're raised, then we actually have to grow the height of the block, since the lines have to be pushed down as though we're placing + // empty lines beside the first letter. + if (dropHeightDelta > 0) + setLogicalHeight(logicalHeight() + dropHeightDelta * heightOfLine); + } + } + return LayoutPoint(floatLogicalLeft, logicalTopOffset); } @@ -2268,19 +2516,18 @@ bool RenderBlockFlow::positionNewFloats() // The float cannot start above the top position of the last positioned float. if (lastPlacedFloatingObject) - logicalTop = std::max(logicalTopForFloat(lastPlacedFloatingObject), logicalTop); + logicalTop = std::max(logicalTopForFloat(*lastPlacedFloatingObject), logicalTop); auto end = floatingObjectSet.end(); // Now walk through the set of unpositioned floats and place them. for (; it != end; ++it) { - FloatingObject* floatingObject = it->get(); + auto& floatingObject = *it->get(); // The containing block is responsible for positioning floats, so if we have floats in our // list that come from somewhere else, do not attempt to position them. - if (floatingObject->renderer().containingBlock() != this) + auto& childBox = floatingObject.renderer(); + if (childBox.containingBlock() != this) continue; - RenderBox& childBox = floatingObject->renderer(); - LayoutUnit childLogicalLeftMargin = style().isLeftToRightDirection() ? marginStartForChild(childBox) : marginEndForChild(childBox); LayoutRect oldRect = childBox.frameRect(); @@ -2299,13 +2546,11 @@ bool RenderBlockFlow::positionNewFloats() estimateRegionRangeForBoxChild(childBox); + childBox.markForPaginationRelayoutIfNeeded(); + childBox.layoutIfNeeded(); + LayoutState* layoutState = view().layoutState(); bool isPaginated = layoutState->isPaginated(); - if (isPaginated && !childBox.needsLayout()) - childBox.markForPaginationRelayoutIfNeeded(); - - childBox.layoutIfNeeded(); - if (isPaginated) { // If we are unsplittable and don't fit, then we need to move down. // We include our margins as part of the unsplittable area. @@ -2314,14 +2559,14 @@ bool RenderBlockFlow::positionNewFloats() // See if we have a pagination strut that is making us move down further. // Note that an unsplittable child can't also have a pagination strut, so this is // exclusive with the case above. - RenderBlock* childBlock = childBox.isRenderBlock() ? toRenderBlock(&childBox) : nullptr; + RenderBlock* childBlock = is<RenderBlock>(childBox) ? &downcast<RenderBlock>(childBox) : nullptr; if (childBlock && childBlock->paginationStrut()) { newLogicalTop += childBlock->paginationStrut(); childBlock->setPaginationStrut(0); } if (newLogicalTop != floatLogicalLocation.y()) { - floatingObject->setPaginationStrut(newLogicalTop - floatLogicalLocation.y()); + floatingObject.setPaginationStrut(newLogicalTop - floatLogicalLocation.y()); floatLogicalLocation = computeLogicalLocationForFloat(floatingObject, newLogicalTop); setLogicalLeftForFloat(floatingObject, floatLogicalLocation.x()); @@ -2342,14 +2587,12 @@ bool RenderBlockFlow::positionNewFloats() setLogicalTopForFloat(floatingObject, floatLogicalLocation.y()); - setLogicalHeightForFloat(floatingObject, logicalHeightForChild(childBox) + marginBeforeForChild(childBox) + marginAfterForChild(childBox)); + setLogicalHeightForFloat(floatingObject, logicalHeightForChildForFragmentation(childBox) + marginBeforeForChild(childBox) + marginAfterForChild(childBox)); - m_floatingObjects->addPlacedObject(floatingObject); + m_floatingObjects->addPlacedObject(&floatingObject); -#if ENABLE(CSS_SHAPES) if (ShapeOutsideInfo* shapeOutside = childBox.shapeOutsideInfo()) - shapeOutside->setShapeSize(logicalWidthForChild(childBox), logicalHeightForChild(childBox)); -#endif + shapeOutside->setReferenceBoxLogicalSize(logicalSizeForChild(childBox)); // If the child moved, we have to repaint it. if (childBox.checkForRepaintDuringLayout()) childBox.repaintDuringLayoutIfMoved(oldRect); @@ -2357,7 +2600,7 @@ bool RenderBlockFlow::positionNewFloats() return true; } -void RenderBlockFlow::newLine(EClear clear) +void RenderBlockFlow::clearFloats(EClear clear) { positionNewFloats(); // set y position @@ -2419,8 +2662,23 @@ LayoutUnit RenderBlockFlow::lowestFloatLogicalBottom(FloatingObject::Type floatT const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto end = floatingObjectSet.end(); for (auto it = floatingObjectSet.begin(); it != end; ++it) { - FloatingObject* floatingObject = it->get(); - if (floatingObject->isPlaced() && floatingObject->type() & floatType) + const auto& floatingObject = *it->get(); + if (floatingObject.isPlaced() && floatingObject.type() & floatType) + lowestFloatBottom = std::max(lowestFloatBottom, logicalBottomForFloat(floatingObject)); + } + return lowestFloatBottom; +} + +LayoutUnit RenderBlockFlow::lowestInitialLetterLogicalBottom() const +{ + if (!m_floatingObjects) + return 0; + LayoutUnit lowestFloatBottom = 0; + const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); + auto end = floatingObjectSet.end(); + for (auto it = floatingObjectSet.begin(); it != end; ++it) { + const auto& floatingObject = *it->get(); + if (floatingObject.isPlaced() && floatingObject.renderer().style().styleType() == FIRST_LETTER && floatingObject.renderer().style().initialLetterDrop() > 0) lowestFloatBottom = std::max(lowestFloatBottom, logicalBottomForFloat(floatingObject)); } return lowestFloatBottom; @@ -2429,7 +2687,7 @@ LayoutUnit RenderBlockFlow::lowestFloatLogicalBottom(FloatingObject::Type floatT LayoutUnit RenderBlockFlow::addOverhangingFloats(RenderBlockFlow& child, bool makeChildPaintOtherFloats) { // Prevent floats from being added to the canvas by the root element, e.g., <html>. - if (child.hasOverflowClip() || !child.containsFloats() || child.isRoot() || child.hasColumns() || child.isWritingModeRoot()) + if (!child.containsFloats() || child.createsNewFormattingContext()) return 0; LayoutUnit childLogicalTop = child.logicalTop(); @@ -2440,14 +2698,14 @@ LayoutUnit RenderBlockFlow::addOverhangingFloats(RenderBlockFlow& child, bool ma // overflow. auto childEnd = child.m_floatingObjects->set().end(); for (auto childIt = child.m_floatingObjects->set().begin(); childIt != childEnd; ++childIt) { - FloatingObject* floatingObject = childIt->get(); + auto& floatingObject = *childIt->get(); LayoutUnit floatLogicalBottom = std::min(logicalBottomForFloat(floatingObject), LayoutUnit::max() - childLogicalTop); LayoutUnit logicalBottom = childLogicalTop + floatLogicalBottom; lowestFloatLogicalBottom = std::max(lowestFloatLogicalBottom, logicalBottom); if (logicalBottom > logicalHeight()) { // If the object is not in the list, we add it now. - if (!containsFloat(floatingObject->renderer())) { + if (!containsFloat(floatingObject.renderer())) { LayoutSize offset = isHorizontalWritingMode() ? LayoutSize(-childLogicalLeft, -childLogicalTop) : LayoutSize(-childLogicalTop, -childLogicalLeft); bool shouldPaint = false; @@ -2455,31 +2713,31 @@ LayoutUnit RenderBlockFlow::addOverhangingFloats(RenderBlockFlow& child, bool ma // behaves properly). We always want to propagate the desire to paint the float as // far out as we can, to the outermost block that overlaps the float, stopping only // if we hit a self-painting layer boundary. - if (floatingObject->renderer().enclosingFloatPaintingLayer() == enclosingFloatPaintingLayer()) { - floatingObject->setShouldPaint(false); + if (floatingObject.renderer().enclosingFloatPaintingLayer() == enclosingFloatPaintingLayer()) { + floatingObject.setShouldPaint(false); shouldPaint = true; } // We create the floating object list lazily. if (!m_floatingObjects) createFloatingObjects(); - m_floatingObjects->add(floatingObject->copyToNewContainer(offset, shouldPaint, true)); + m_floatingObjects->add(floatingObject.copyToNewContainer(offset, shouldPaint, true)); } } else { - if (makeChildPaintOtherFloats && !floatingObject->shouldPaint() && !floatingObject->renderer().hasSelfPaintingLayer() - && floatingObject->renderer().isDescendantOf(&child) && floatingObject->renderer().enclosingFloatPaintingLayer() == child.enclosingFloatPaintingLayer()) { + const auto& renderer = floatingObject.renderer(); + if (makeChildPaintOtherFloats && !floatingObject.shouldPaint() && !renderer.hasSelfPaintingLayer() + && renderer.isDescendantOf(&child) && renderer.enclosingFloatPaintingLayer() == child.enclosingFloatPaintingLayer()) { // The float is not overhanging from this block, so if it is a descendant of the child, the child should // paint it (the other case is that it is intruding into the child), unless it has its own layer or enclosing // layer. // If makeChildPaintOtherFloats is false, it means that the child must already know about all the floats // it should paint. - floatingObject->setShouldPaint(true); + floatingObject.setShouldPaint(true); } - // Since the float doesn't overhang, it didn't get put into our list. We need to go ahead and add its overflow in to the - // child now. - if (floatingObject->isDescendant()) - child.addOverflowFromChild(&floatingObject->renderer(), LayoutSize(xPositionForFloatIncludingMargin(floatingObject), yPositionForFloatIncludingMargin(floatingObject))); + // Since the float doesn't overhang, it didn't get put into our list. We need to add its overflow in to the child now. + if (floatingObject.isDescendant()) + child.addOverflowFromChild(&renderer, LayoutSize(xPositionForFloatIncludingMargin(floatingObject), yPositionForFloatIncludingMargin(floatingObject))); } } return lowestFloatLogicalBottom; @@ -2487,21 +2745,25 @@ LayoutUnit RenderBlockFlow::addOverhangingFloats(RenderBlockFlow& child, bool ma bool RenderBlockFlow::hasOverhangingFloat(RenderBox& renderer) { - if (!m_floatingObjects || hasColumns() || !parent()) + if (!m_floatingObjects || !parent()) return false; const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); - auto it = floatingObjectSet.find<RenderBox&, FloatingObjectHashTranslator>(renderer); + const auto it = floatingObjectSet.find<RenderBox&, FloatingObjectHashTranslator>(renderer); if (it == floatingObjectSet.end()) return false; - return logicalBottomForFloat(it->get()) > logicalHeight(); + return logicalBottomForFloat(*it->get()) > logicalHeight(); } -void RenderBlockFlow::addIntrudingFloats(RenderBlockFlow* prev, LayoutUnit logicalLeftOffset, LayoutUnit logicalTopOffset) +void RenderBlockFlow::addIntrudingFloats(RenderBlockFlow* prev, RenderBlockFlow* container, LayoutUnit logicalLeftOffset, LayoutUnit logicalTopOffset) { ASSERT(!avoidsFloats()); + // If we create our own block formatting context then our contents don't interact with floats outside it, even those from our parent. + if (createsNewFormattingContext()) + return; + // If the parent or previous sibling doesn't have any floats to add, don't bother. if (!prev->m_floatingObjects) return; @@ -2511,9 +2773,9 @@ void RenderBlockFlow::addIntrudingFloats(RenderBlockFlow* prev, LayoutUnit logic const FloatingObjectSet& prevSet = prev->m_floatingObjects->set(); auto prevEnd = prevSet.end(); for (auto prevIt = prevSet.begin(); prevIt != prevEnd; ++prevIt) { - FloatingObject* floatingObject = prevIt->get(); + auto& floatingObject = *prevIt->get(); if (logicalBottomForFloat(floatingObject) > logicalTopOffset) { - if (!m_floatingObjects || !m_floatingObjects->set().contains<FloatingObject&, FloatingObjectHashTranslator>(*floatingObject)) { + if (!m_floatingObjects || !m_floatingObjects->set().contains<FloatingObject&, FloatingObjectHashTranslator>(floatingObject)) { // We create the floating object list lazily. if (!m_floatingObjects) createFloatingObjects(); @@ -2524,10 +2786,10 @@ void RenderBlockFlow::addIntrudingFloats(RenderBlockFlow* prev, LayoutUnit logic // into account. Only apply this code if prev is the parent, since otherwise the left margin // will get applied twice. LayoutSize offset = isHorizontalWritingMode() - ? LayoutSize(logicalLeftOffset - (prev != parent() ? prev->marginLeft() : LayoutUnit()), logicalTopOffset) - : LayoutSize(logicalTopOffset, logicalLeftOffset - (prev != parent() ? prev->marginTop() : LayoutUnit())); + ? LayoutSize(logicalLeftOffset - (prev != container ? prev->marginLeft() : LayoutUnit()), logicalTopOffset) + : LayoutSize(logicalTopOffset, logicalLeftOffset - (prev != container ? prev->marginTop() : LayoutUnit())); - m_floatingObjects->add(floatingObject->copyToNewContainer(offset)); + m_floatingObjects->add(floatingObject.copyToNewContainer(offset)); } } } @@ -2543,17 +2805,19 @@ void RenderBlockFlow::markAllDescendantsWithFloatsForLayout(RenderBox* floatToRe if (floatToRemove) removeFloatingObject(*floatToRemove); + else if (childrenInline()) + return; // Iterate over our block children and mark them as needed. for (auto& block : childrenOfType<RenderBlock>(*this)) { if (!floatToRemove && block.isFloatingOrOutOfFlowPositioned()) continue; - if (!block.isRenderBlockFlow()) { + if (!is<RenderBlockFlow>(block)) { if (block.shrinkToAvoidFloats() && block.everHadLayout()) block.setChildNeedsLayout(markParents); continue; } - auto& blockFlow = toRenderBlockFlow(block); + auto& blockFlow = downcast<RenderBlockFlow>(block); if ((floatToRemove ? blockFlow.containsFloat(*floatToRemove) : blockFlow.containsFloats()) || blockFlow.shrinkToAvoidFloats()) blockFlow.markAllDescendantsWithFloatsForLayout(floatToRemove, inLayout); } @@ -2568,21 +2832,21 @@ void RenderBlockFlow::markSiblingsWithFloatsForLayout(RenderBox* floatToRemove) auto end = floatingObjectSet.end(); for (RenderObject* next = nextSibling(); next; next = next->nextSibling()) { - if (!next->isRenderBlockFlow() || next->isFloatingOrOutOfFlowPositioned() || toRenderBlock(next)->avoidsFloats()) + if (!is<RenderBlockFlow>(*next) || next->isFloatingOrOutOfFlowPositioned()) continue; - RenderBlockFlow* nextBlock = toRenderBlockFlow(next); + RenderBlockFlow& nextBlock = downcast<RenderBlockFlow>(*next); for (auto it = floatingObjectSet.begin(); it != end; ++it) { RenderBox& floatingBox = (*it)->renderer(); if (floatToRemove && &floatingBox != floatToRemove) continue; - if (nextBlock->containsFloat(floatingBox)) - nextBlock->markAllDescendantsWithFloatsForLayout(&floatingBox); + if (nextBlock.containsFloat(floatingBox)) + nextBlock.markAllDescendantsWithFloatsForLayout(&floatingBox); } } } -LayoutPoint RenderBlockFlow::flipFloatForWritingModeForChild(const FloatingObject* child, const LayoutPoint& point) const +LayoutPoint RenderBlockFlow::flipFloatForWritingModeForChild(const FloatingObject& child, const LayoutPoint& point) const { if (!style().isFlippedBlocksWritingMode()) return point; @@ -2591,8 +2855,8 @@ LayoutPoint RenderBlockFlow::flipFloatForWritingModeForChild(const FloatingObjec // it's going to get added back in. We hide this complication here so that the calling code looks normal for the unflipped // case. if (isHorizontalWritingMode()) - return LayoutPoint(point.x(), point.y() + height() - child->renderer().height() - 2 * yPositionForFloatIncludingMargin(child)); - return LayoutPoint(point.x() + width() - child->renderer().width() - 2 * xPositionForFloatIncludingMargin(child), point.y()); + return LayoutPoint(point.x(), point.y() + height() - child.renderer().height() - 2 * yPositionForFloatIncludingMargin(child)); + return LayoutPoint(point.x() + width() - child.renderer().width() - 2 * xPositionForFloatIncludingMargin(child), point.y()); } LayoutUnit RenderBlockFlow::getClearDelta(RenderBox& child, LayoutUnit logicalTop) @@ -2623,7 +2887,7 @@ LayoutUnit RenderBlockFlow::getClearDelta(RenderBox& child, LayoutUnit logicalTo if (!result && child.avoidsFloats()) { LayoutUnit newLogicalTop = logicalTop; while (true) { - LayoutUnit availableLogicalWidthAtNewLogicalTopOffset = availableLogicalWidthForLine(newLogicalTop, false, logicalHeightForChild(child)); + LayoutUnit availableLogicalWidthAtNewLogicalTopOffset = availableLogicalWidthForLine(newLogicalTop, DoNotIndentText, logicalHeightForChild(child)); if (availableLogicalWidthAtNewLogicalTopOffset == availableLogicalWidthForContent(newLogicalTop)) return newLogicalTop - logicalTop; @@ -2673,19 +2937,20 @@ bool RenderBlockFlow::hitTestFloats(const HitTestRequest& request, HitTestResult return false; LayoutPoint adjustedLocation = accumulatedOffset; - if (isRenderView()) - adjustedLocation += toLayoutSize(toRenderView(*this).frameView().scrollPosition()); + if (is<RenderView>(*this)) + adjustedLocation += toLayoutSize(downcast<RenderView>(*this).frameView().scrollPosition()); const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto begin = floatingObjectSet.begin(); for (auto it = floatingObjectSet.end(); it != begin;) { --it; - FloatingObject* floatingObject = it->get(); - if (floatingObject->shouldPaint() && !floatingObject->renderer().hasSelfPaintingLayer()) { - LayoutUnit xOffset = xPositionForFloatIncludingMargin(floatingObject) - floatingObject->renderer().x(); - LayoutUnit yOffset = yPositionForFloatIncludingMargin(floatingObject) - floatingObject->renderer().y(); + const auto& floatingObject = *it->get(); + auto& renderer = floatingObject.renderer(); + if (floatingObject.shouldPaint() && !renderer.hasSelfPaintingLayer()) { + LayoutUnit xOffset = xPositionForFloatIncludingMargin(floatingObject) - renderer.x(); + LayoutUnit yOffset = yPositionForFloatIncludingMargin(floatingObject) - renderer.y(); LayoutPoint childPoint = flipFloatForWritingModeForChild(floatingObject, adjustedLocation + LayoutSize(xOffset, yOffset)); - if (floatingObject->renderer().hitTest(request, result, locationInContainer, childPoint)) { + if (renderer.hitTest(request, result, locationInContainer, childPoint)) { updateHitTestResult(result, locationInContainer.point() - toLayoutSize(childPoint)); return true; } @@ -2699,8 +2964,8 @@ bool RenderBlockFlow::hitTestInlineChildren(const HitTestRequest& request, HitTe { ASSERT(childrenInline()); - if (m_simpleLineLayout) - return SimpleLineLayout::hitTestFlow(*this, *m_simpleLineLayout, request, result, locationInContainer, accumulatedOffset, hitTestAction); + if (auto simpleLineLayout = this->simpleLineLayout()) + return SimpleLineLayout::hitTestFlow(*this, *simpleLineLayout, request, result, locationInContainer, accumulatedOffset, hitTestAction); return m_lineBoxes.hitTest(this, request, result, locationInContainer, accumulatedOffset, hitTestAction); } @@ -2715,17 +2980,17 @@ void RenderBlockFlow::adjustForBorderFit(LayoutUnit x, LayoutUnit& left, LayoutU if (childrenInline()) { const_cast<RenderBlockFlow&>(*this).ensureLineBoxes(); - for (auto box = firstRootBox(); box; box = box->nextRootBox()) { + for (auto* box = firstRootBox(); box; box = box->nextRootBox()) { if (box->firstChild()) - left = std::min(left, x + static_cast<LayoutUnit>(box->firstChild()->x())); + left = std::min(left, x + LayoutUnit(box->firstChild()->x())); if (box->lastChild()) - right = std::max(right, x + static_cast<LayoutUnit>(ceilf(box->lastChild()->logicalRight()))); + right = std::max(right, x + LayoutUnit(ceilf(box->lastChild()->logicalRight()))); } } else { for (RenderBox* obj = firstChildBox(); obj; obj = obj->nextSiblingBox()) { if (!obj->isFloatingOrOutOfFlowPositioned()) { - if (obj->isRenderBlockFlow() && !obj->hasOverflowClip()) - toRenderBlockFlow(obj)->adjustForBorderFit(x + obj->x(), left, right); + if (is<RenderBlockFlow>(*obj) && !obj->hasOverflowClip()) + downcast<RenderBlockFlow>(*obj).adjustForBorderFit(x + obj->x(), left, right); else if (obj->style().visibility() == VISIBLE) { // We are a replaced element or some kind of non-block-flow object. left = std::min(left, x + obj->x()); @@ -2739,11 +3004,11 @@ void RenderBlockFlow::adjustForBorderFit(LayoutUnit x, LayoutUnit& left, LayoutU const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto end = floatingObjectSet.end(); for (auto it = floatingObjectSet.begin(); it != end; ++it) { - FloatingObject* r = it->get(); + const auto& floatingObject = *it->get(); // Only examine the object if our m_shouldPaint flag is set. - if (r->shouldPaint()) { - LayoutUnit floatLeft = xPositionForFloatIncludingMargin(r) - r->renderer().x(); - LayoutUnit floatRight = floatLeft + r->renderer().width(); + if (floatingObject.shouldPaint()) { + LayoutUnit floatLeft = xPositionForFloatIncludingMargin(floatingObject) - floatingObject.renderer().x(); + LayoutUnit floatRight = floatLeft + floatingObject.renderer().width(); left = std::min(left, floatLeft); right = std::max(right, floatRight); } @@ -2753,7 +3018,7 @@ void RenderBlockFlow::adjustForBorderFit(LayoutUnit x, LayoutUnit& left, LayoutU void RenderBlockFlow::fitBorderToLinesIfNeeded() { - if (style().borderFit() == BorderFitBorder || hasOverrideWidth()) + if (style().borderFit() == BorderFitBorder || hasOverrideLogicalContentWidth()) return; // Walk any normal flow lines to snugly fit. @@ -2782,6 +3047,12 @@ void RenderBlockFlow::markLinesDirtyInBlockRange(LayoutUnit logicalTop, LayoutUn if (logicalTop >= logicalBottom) return; + // Floats currently affect the choice whether to use simple line layout path. + if (m_simpleLineLayout) { + invalidateLineLayoutPath(); + return; + } + RootInlineBox* lowestDirtyLine = lastRootBox(); RootInlineBox* afterLowest = lowestDirtyLine; while (lowestDirtyLine && lowestDirtyLine->lineBottomWithLeading() >= logicalBottom && logicalBottom < LayoutUnit::max()) { @@ -2795,47 +3066,66 @@ void RenderBlockFlow::markLinesDirtyInBlockRange(LayoutUnit logicalTop, LayoutUn } } -int RenderBlockFlow::firstLineBaseline() const +std::optional<int> RenderBlockFlow::firstLineBaseline() const { if (isWritingModeRoot() && !isRubyRun()) - return -1; + return std::optional<int>(); if (!childrenInline()) return RenderBlock::firstLineBaseline(); if (!hasLines()) - return -1; + return std::optional<int>(); - if (m_simpleLineLayout) - return SimpleLineLayout::computeFlowFirstLineBaseline(*this, *m_simpleLineLayout); + if (auto simpleLineLayout = this->simpleLineLayout()) + return std::optional<int>(SimpleLineLayout::computeFlowFirstLineBaseline(*this, *simpleLineLayout)); ASSERT(firstRootBox()); return firstRootBox()->logicalTop() + firstLineStyle().fontMetrics().ascent(firstRootBox()->baselineType()); } -int RenderBlockFlow::inlineBlockBaseline(LineDirectionMode lineDirection) const +std::optional<int> RenderBlockFlow::inlineBlockBaseline(LineDirectionMode lineDirection) const { if (isWritingModeRoot() && !isRubyRun()) - return -1; - - if (!childrenInline()) - return RenderBlock::inlineBlockBaseline(lineDirection); + return std::optional<int>(); + + // Note that here we only take the left and bottom into consideration. Our caller takes the right and top into consideration. + float boxHeight = lineDirection == HorizontalLine ? height() + m_marginBox.bottom() : width() + m_marginBox.left(); + float lastBaseline; + if (!childrenInline()) { + std::optional<int> inlineBlockBaseline = RenderBlock::inlineBlockBaseline(lineDirection); + if (!inlineBlockBaseline) + return inlineBlockBaseline; + lastBaseline = inlineBlockBaseline.value(); + } else { + if (!hasLines()) { + if (!hasLineIfEmpty()) + return std::optional<int>(); + const auto& fontMetrics = firstLineStyle().fontMetrics(); + return std::optional<int>(fontMetrics.ascent() + + (lineHeight(true, lineDirection, PositionOfInteriorLineBoxes) - fontMetrics.height()) / 2 + + (lineDirection == HorizontalLine ? borderTop() + paddingTop() : borderRight() + paddingRight())); + } - if (!hasLines()) { - if (!hasLineIfEmpty()) - return -1; - const FontMetrics& fontMetrics = firstLineStyle().fontMetrics(); - return fontMetrics.ascent() - + (lineHeight(true, lineDirection, PositionOfInteriorLineBoxes) - fontMetrics.height()) / 2 - + (lineDirection == HorizontalLine ? borderTop() + paddingTop() : borderRight() + paddingRight()); + if (auto simpleLineLayout = this->simpleLineLayout()) + lastBaseline = SimpleLineLayout::computeFlowLastLineBaseline(*this, *simpleLineLayout); + else { + bool isFirstLine = lastRootBox() == firstRootBox(); + const auto& style = isFirstLine ? firstLineStyle() : this->style(); + lastBaseline = lastRootBox()->logicalTop() + style.fontMetrics().ascent(lastRootBox()->baselineType()); + } } + // According to the CSS spec http://www.w3.org/TR/CSS21/visudet.html, we shouldn't be performing this min, but should + // instead be returning boxHeight directly. However, we feel that a min here is better behavior (and is consistent + // enough with the spec to not cause tons of breakages). + return std::optional<int>(style().overflowY() == OVISIBLE ? lastBaseline : std::min(boxHeight, lastBaseline)); +} - if (m_simpleLineLayout) - return SimpleLineLayout::computeFlowLastLineBaseline(*this, *m_simpleLineLayout); - - bool isFirstLine = lastRootBox() == firstRootBox(); - const RenderStyle& style = isFirstLine ? firstLineStyle() : this->style(); - return lastRootBox()->logicalTop() + style.fontMetrics().ascent(lastRootBox()->baselineType()); +void RenderBlockFlow::setSelectionState(SelectionState state) +{ + if (state != SelectionNone) + ensureLineBoxes(); + RenderBoxModelObject::setSelectionState(state); } GapRects RenderBlockFlow::inlineSelectionGaps(RenderBlock& rootBlock, const LayoutPoint& rootBlockPhysicalPosition, const LayoutSize& offsetFromRootBlock, @@ -2849,8 +3139,7 @@ GapRects RenderBlockFlow::inlineSelectionGaps(RenderBlock& rootBlock, const Layo if (!hasLines()) { if (containsStart) { - // Go ahead and update our lastLogicalTop to be the bottom of the block. <hr>s or empty blocks with height can trip this - // case. + // Update our lastLogicalTop to be the bottom of the block. <hr>s or empty blocks with height can trip this case. lastLogicalTop = blockDirectionOffset(rootBlock, offsetFromRootBlock) + logicalHeight(); lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, logicalHeight(), cache); lastLogicalRight = logicalRightSelectionOffset(rootBlock, logicalHeight(), cache); @@ -2868,7 +3157,7 @@ GapRects RenderBlockFlow::inlineSelectionGaps(RenderBlock& rootBlock, const Layo LayoutUnit selHeight = curr->selectionHeightAdjustedForPrecedingBlock(); if (!containsStart && !lastSelectedLine && - selectionState() != SelectionStart && selectionState() != SelectionBoth) + selectionState() != SelectionStart && selectionState() != SelectionBoth && !isRubyBase()) result.uniteCenter(blockSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight, selTop, cache, paintInfo)); LayoutRect logicalRect(curr->logicalLeft(), selTop, curr->logicalWidth(), selTop + selHeight); @@ -2886,7 +3175,7 @@ GapRects RenderBlockFlow::inlineSelectionGaps(RenderBlock& rootBlock, const Layo lastSelectedLine = lastRootBox(); if (lastSelectedLine && selectionState() != SelectionEnd && selectionState() != SelectionBoth) { - // Go ahead and update our lastY to be the bottom of the last selected line. + // Update our lastY to be the bottom of the last selected line. lastLogicalTop = blockDirectionOffset(rootBlock, offsetFromRootBlock) + lastSelectedLine->selectionBottom(); lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, lastSelectedLine->selectionBottom(), cache); lastLogicalRight = logicalRightSelectionOffset(rootBlock, lastSelectedLine->selectionBottom(), cache); @@ -2896,17 +3185,29 @@ GapRects RenderBlockFlow::inlineSelectionGaps(RenderBlock& rootBlock, const Layo void RenderBlockFlow::createRenderNamedFlowFragmentIfNeeded() { - if (!document().cssRegionsEnabled() || renderNamedFlowFragment() || isRenderNamedFlowFragment()) + if (renderNamedFlowFragment() || isRenderNamedFlowFragment()) return; - if (style().isDisplayRegionType() && style().hasFlowFrom()) { + // FIXME: Multicolumn regions not yet supported (http://dev.w3.org/csswg/css-regions/#multi-column-regions) + if (style().isDisplayRegionType() && style().hasFlowFrom() && !style().specifiesColumns()) { RenderNamedFlowFragment* flowFragment = new RenderNamedFlowFragment(document(), RenderNamedFlowFragment::createStyle(style())); flowFragment->initializeStyle(); + addChild(flowFragment); setRenderNamedFlowFragment(flowFragment); - addChild(renderNamedFlowFragment()); } } +bool RenderBlockFlow::needsLayoutAfterRegionRangeChange() const +{ + // A block without floats or that expands to enclose them won't need a relayout + // after a region range change. There is no overflow content needing relayout + // in the region chain because the region range can only shrink after the estimation. + if (!containsFloats() || createsNewFormattingContext()) + return false; + + return true; +} + bool RenderBlockFlow::canHaveChildren() const { return !renderNamedFlowFragment() ? RenderBlock::canHaveChildren() : renderNamedFlowFragment()->canHaveChildren(); @@ -2929,27 +3230,31 @@ void RenderBlockFlow::updateLogicalHeight() { RenderBlock::updateLogicalHeight(); - if (renderNamedFlowFragment()) + if (renderNamedFlowFragment()) { renderNamedFlowFragment()->setLogicalHeight(std::max<LayoutUnit>(0, logicalHeight() - borderAndPaddingLogicalHeight())); + renderNamedFlowFragment()->invalidateRegionIfNeeded(); + } } void RenderBlockFlow::setRenderNamedFlowFragment(RenderNamedFlowFragment* flowFragment) { RenderBlockFlowRareData& rareData = ensureRareBlockFlowData(); - if (rareData.m_renderNamedFlowFragment) - rareData.m_renderNamedFlowFragment->destroy(); + if (auto* flowFragmentOnFlow = std::exchange(rareData.m_renderNamedFlowFragment, nullptr)) + flowFragmentOnFlow->destroy(); rareData.m_renderNamedFlowFragment = flowFragment; } void RenderBlockFlow::setMultiColumnFlowThread(RenderMultiColumnFlowThread* flowThread) { - RenderBlockFlowRareData& rareData = ensureRareBlockFlowData(); - rareData.m_multiColumnFlowThread = flowThread; + if (flowThread || hasRareBlockFlowData()) { + RenderBlockFlowRareData& rareData = ensureRareBlockFlowData(); + rareData.m_multiColumnFlowThread = flowThread; + } } static bool shouldCheckLines(const RenderBlockFlow& blockFlow) { - return !blockFlow.isFloatingOrOutOfFlowPositioned() && !blockFlow.isRunIn() && blockFlow.style().height().isAuto(); + return !blockFlow.isFloatingOrOutOfFlowPositioned() && blockFlow.style().height().isAuto(); } RootInlineBox* RenderBlockFlow::lineAtIndex(int i) const @@ -2960,7 +3265,7 @@ RootInlineBox* RenderBlockFlow::lineAtIndex(int i) const return nullptr; if (childrenInline()) { - for (auto box = firstRootBox(); box; box = box->nextRootBox()) { + for (auto* box = firstRootBox(); box; box = box->nextRootBox()) { if (!i--) return box; } @@ -2985,8 +3290,12 @@ int RenderBlockFlow::lineCount(const RootInlineBox* stopRootInlineBox, bool* fou int count = 0; if (childrenInline()) { - for (auto box = firstRootBox(); box; box = box->nextRootBox()) { - count++; + if (auto simpleLineLayout = this->simpleLineLayout()) { + ASSERT(!stopRootInlineBox); + return simpleLineLayout->lineCount(); + } + for (auto* box = firstRootBox(); box; box = box->nextRootBox()) { + ++count; if (box == stopRootInlineBox) { if (found) *found = true; @@ -3017,18 +3326,18 @@ static int getHeightForLineCount(const RenderBlockFlow& block, int lineCount, bo return -1; if (block.childrenInline()) { - for (auto box = block.firstRootBox(); box; box = box->nextRootBox()) { + for (auto* box = block.firstRootBox(); box; box = box->nextRootBox()) { if (++count == lineCount) return box->lineBottom() + (includeBottom ? (block.borderBottom() + block.paddingBottom()) : LayoutUnit()); } } else { - RenderBox* normalFlowChildWithoutLines = 0; - for (auto obj = block.firstChildBox(); obj; obj = obj->nextSiblingBox()) { - if (obj->isRenderBlockFlow() && shouldCheckLines(toRenderBlockFlow(*obj))) { - int result = getHeightForLineCount(toRenderBlockFlow(*obj), lineCount, false, count); + RenderBox* normalFlowChildWithoutLines = nullptr; + for (auto* obj = block.firstChildBox(); obj; obj = obj->nextSiblingBox()) { + if (is<RenderBlockFlow>(*obj) && shouldCheckLines(downcast<RenderBlockFlow>(*obj))) { + int result = getHeightForLineCount(downcast<RenderBlockFlow>(*obj), lineCount, false, count); if (result != -1) return result + obj->y() + (includeBottom ? (block.borderBottom() + block.paddingBottom()) : LayoutUnit()); - } else if (!obj->isFloatingOrOutOfFlowPositioned() && !obj->isRunIn()) + } else if (!obj->isFloatingOrOutOfFlowPositioned()) normalFlowChildWithoutLines = obj; } if (normalFlowChildWithoutLines && !lineCount) @@ -3053,7 +3362,7 @@ void RenderBlockFlow::clearTruncation() ensureLineBoxes(); setHasMarkupTruncation(false); - for (auto box = firstRootBox(); box; box = box->nextRootBox()) + for (auto* box = firstRootBox(); box; box = box->nextRootBox()) box->clearTruncation(); return; } @@ -3066,8 +3375,8 @@ void RenderBlockFlow::clearTruncation() bool RenderBlockFlow::containsNonZeroBidiLevel() const { - for (auto root = firstRootBox(); root; root = root->nextRootBox()) { - for (auto box = root->firstLeafChild(); box; box = box->nextLeafChild()) { + for (auto* root = firstRootBox(); root; root = root->nextRootBox()) { + for (auto* box = root->firstLeafChild(); box; box = box->nextLeafChild()) { if (box->bidiLevel()) return true; } @@ -3083,14 +3392,79 @@ Position RenderBlockFlow::positionForBox(InlineBox *box, bool start) const if (!box->renderer().nonPseudoNode()) return createLegacyEditingPosition(nonPseudoElement(), start ? caretMinOffset() : caretMaxOffset()); - if (!box->isInlineTextBox()) + if (!is<InlineTextBox>(*box)) return createLegacyEditingPosition(box->renderer().nonPseudoNode(), start ? box->renderer().caretMinOffset() : box->renderer().caretMaxOffset()); - InlineTextBox* textBox = toInlineTextBox(box); - return createLegacyEditingPosition(box->renderer().nonPseudoNode(), start ? textBox->start() : textBox->start() + textBox->len()); + auto& textBox = downcast<InlineTextBox>(*box); + return createLegacyEditingPosition(textBox.renderer().nonPseudoNode(), start ? textBox.start() : textBox.start() + textBox.len()); +} + +RenderText* RenderBlockFlow::findClosestTextAtAbsolutePoint(const FloatPoint& point) +{ + // A light, non-recursive version of RenderBlock::positionForCoordinates that looks at + // whether a point lies within the gaps between its root line boxes, to be called against + // a node returned from elementAtPoint. We make the assumption that either the node or one + // of its immediate children contains the root line boxes in question. + // See <rdar://problem/6824650> for context. + + RenderBlock* block = this; + + FloatPoint localPoint = block->absoluteToLocal(point); + + if (!block->childrenInline()) { + // Look among our immediate children for an alternate box that contains the point. + for (RenderBox* child = block->firstChildBox(); child; child = child->nextSiblingBox()) { + if (!child->height() || child->style().visibility() != WebCore::VISIBLE || child->isFloatingOrOutOfFlowPositioned()) + continue; + float top = child->y(); + + RenderBox* nextChild = child->nextSiblingBox(); + while (nextChild && nextChild->isFloatingOrOutOfFlowPositioned()) + nextChild = nextChild->nextSiblingBox(); + if (!nextChild) { + if (localPoint.y() >= top) { + block = downcast<RenderBlock>(child); + break; + } + continue; + } + + float bottom = nextChild->y(); + + if (localPoint.y() >= top && localPoint.y() < bottom && is<RenderBlock>(*child)) { + block = downcast<RenderBlock>(child); + break; + } + } + + if (!block->childrenInline()) + return nullptr; + + localPoint = block->absoluteToLocal(point); + } + + RenderBlockFlow& blockFlow = downcast<RenderBlockFlow>(*block); + + // Only check the gaps between the root line boxes. We deliberately ignore overflow because + // experience has shown that hit tests on an exploded text node can fail when within the + // overflow region. + for (RootInlineBox* current = blockFlow.firstRootBox(); current && current != blockFlow.lastRootBox(); current = current->nextRootBox()) { + float currentBottom = current->y() + current->logicalHeight(); + if (localPoint.y() < currentBottom) + return nullptr; + + RootInlineBox* next = current->nextRootBox(); + float nextTop = next->y(); + if (localPoint.y() < nextTop) { + InlineBox* inlineBox = current->closestLeafChildForLogicalLeftPosition(localPoint.x()); + if (inlineBox && inlineBox->behavesLikeText() && is<RenderText>(inlineBox->renderer())) + return &downcast<RenderText>(inlineBox->renderer()); + } + } + return nullptr; } -VisiblePosition RenderBlockFlow::positionForPointWithInlineChildren(const LayoutPoint& pointInLogicalContents) +VisiblePosition RenderBlockFlow::positionForPointWithInlineChildren(const LayoutPoint& pointInLogicalContents, const RenderRegion* region) { ASSERT(childrenInline()); @@ -3107,6 +3481,9 @@ VisiblePosition RenderBlockFlow::positionForPointWithInlineChildren(const Layout RootInlineBox* firstRootBoxWithChildren = 0; RootInlineBox* lastRootBoxWithChildren = 0; for (RootInlineBox* root = firstRootBox(); root; root = root->nextRootBox()) { + if (region && root->containingRegion() != region) + continue; + if (!root->firstLeafChild()) continue; if (!firstRootBoxWithChildren) @@ -3162,8 +3539,8 @@ VisiblePosition RenderBlockFlow::positionForPointWithInlineChildren(const Layout if (!isHorizontalWritingMode()) point = point.transposedPoint(); if (closestBox->renderer().isReplaced()) - return positionForPointRespectingEditingBoundaries(*this, toRenderBox(closestBox->renderer()), point); - return closestBox->renderer().positionForPoint(point); + return positionForPointRespectingEditingBoundaries(*this, downcast<RenderBox>(closestBox->renderer()), point); + return closestBox->renderer().positionForPoint(point, nullptr); } if (lastRootBoxWithChildren) { @@ -3180,26 +3557,30 @@ VisiblePosition RenderBlockFlow::positionForPointWithInlineChildren(const Layout return createVisiblePosition(0, DOWNSTREAM); } -VisiblePosition RenderBlockFlow::positionForPoint(const LayoutPoint& point) +Position RenderBlockFlow::positionForPoint(const LayoutPoint& point) { - if (auto fragment = renderNamedFlowFragment()) - return fragment->positionForPoint(point); - return RenderBlock::positionForPoint(point); + // FIXME: It supports single text child only (which is the majority of simple line layout supported content at this point). + if (!simpleLineLayout() || firstChild() != lastChild() || !is<RenderText>(firstChild())) + return positionForPoint(point, nullptr).deepEquivalent(); + return downcast<RenderText>(*firstChild()).positionForPoint(point); } +VisiblePosition RenderBlockFlow::positionForPoint(const LayoutPoint& point, const RenderRegion* region) +{ + if (auto fragment = renderNamedFlowFragment()) + return fragment->positionForPoint(point, region); + return RenderBlock::positionForPoint(point, region); +} -void RenderBlockFlow::addFocusRingRectsForInlineChildren(Vector<IntRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject*) +void RenderBlockFlow::addFocusRingRectsForInlineChildren(Vector<LayoutRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject*) { ASSERT(childrenInline()); - - ensureLineBoxes(); - for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { LayoutUnit top = std::max<LayoutUnit>(curr->lineTop(), curr->top()); LayoutUnit bottom = std::min<LayoutUnit>(curr->lineBottom(), curr->top() + curr->height()); LayoutRect rect(additionalOffset.x() + curr->x(), additionalOffset.y() + top, curr->width(), bottom - top); if (!rect.isEmpty()) - rects.append(pixelSnappedIntRect(rect)); + rects.append(rect); } } @@ -3207,69 +3588,19 @@ void RenderBlockFlow::paintInlineChildren(PaintInfo& paintInfo, const LayoutPoin { ASSERT(childrenInline()); - if (m_simpleLineLayout) { - SimpleLineLayout::paintFlow(*this, *m_simpleLineLayout, paintInfo, paintOffset); + if (auto simpleLineLayout = this->simpleLineLayout()) { + SimpleLineLayout::paintFlow(*this, *simpleLineLayout, paintInfo, paintOffset); return; } m_lineBoxes.paint(this, paintInfo, paintOffset); } -bool RenderBlockFlow::relayoutForPagination(bool hasSpecifiedPageLogicalHeight, LayoutUnit pageLogicalHeight, LayoutStateMaintainer& statePusher) +bool RenderBlockFlow::relayoutForPagination(LayoutStateMaintainer& statePusher) { - if (!hasColumns() && !multiColumnFlowThread()) - return false; - - if (hasColumns()) { - RefPtr<RenderOverflow> savedOverflow = m_overflow.release(); - if (childrenInline()) - addOverflowFromInlineChildren(); - else - addOverflowFromBlockChildren(); - LayoutUnit layoutOverflowLogicalBottom = (isHorizontalWritingMode() ? layoutOverflowRect().maxY() : layoutOverflowRect().maxX()) - borderAndPaddingBefore(); - - // FIXME: We don't balance properly at all in the presence of forced page breaks. We need to understand what - // the distance between forced page breaks is so that we can avoid making the minimum column height too tall. - ColumnInfo* colInfo = columnInfo(); - if (!hasSpecifiedPageLogicalHeight) { - LayoutUnit columnHeight = pageLogicalHeight; - int minColumnCount = colInfo->forcedBreaks() + 1; - int desiredColumnCount = colInfo->desiredColumnCount(); - if (minColumnCount >= desiredColumnCount) { - // The forced page breaks are in control of the balancing. Just set the column height to the - // maximum page break distance. - if (!pageLogicalHeight) { - LayoutUnit distanceBetweenBreaks = std::max<LayoutUnit>(colInfo->maximumDistanceBetweenForcedBreaks(), - view().layoutState()->pageLogicalOffset(this, borderAndPaddingBefore() + layoutOverflowLogicalBottom) - colInfo->forcedBreakOffset()); - columnHeight = std::max(colInfo->minimumColumnHeight(), distanceBetweenBreaks); - } - } else if (layoutOverflowLogicalBottom > boundedMultiply(pageLogicalHeight, desiredColumnCount)) { - // Now that we know the intrinsic height of the columns, we have to rebalance them. - columnHeight = std::max<LayoutUnit>(colInfo->minimumColumnHeight(), ceilf((float)layoutOverflowLogicalBottom / desiredColumnCount)); - } - - if (columnHeight && columnHeight != pageLogicalHeight) { - statePusher.pop(); - setEverHadLayout(true); - layoutBlock(false, columnHeight); - return true; - } - } - - if (pageLogicalHeight) - colInfo->setColumnCountAndHeight(ceilf((float)layoutOverflowLogicalBottom / pageLogicalHeight), pageLogicalHeight); - - if (columnCount(colInfo)) { - setLogicalHeight(borderAndPaddingBefore() + colInfo->columnHeight() + borderAndPaddingAfter() + scrollbarLogicalHeight()); - clearOverflow(); - } else - m_overflow = savedOverflow.release(); - return false; - } - - if (!multiColumnFlowThread()->shouldRelayoutForPagination()) + if (!multiColumnFlowThread() || !multiColumnFlowThread()->shouldRelayoutForPagination()) return false; - multiColumnFlowThread()->setNeedsRebalancing(false); + multiColumnFlowThread()->setNeedsHeightsRecalculation(false); multiColumnFlowThread()->setInBalancingPass(true); // Prevent re-entering this method (and recursion into layout). bool needsRelayout; @@ -3282,15 +3613,17 @@ bool RenderBlockFlow::relayoutForPagination(bool hasSpecifiedPageLogicalHeight, // passes than that, though, but the number of retries should not exceed the number of // columns, unless we have a bug. needsRelayout = false; - for (RenderBox* childBox = firstChildBox(); childBox; childBox = childBox->nextSiblingBox()) - if (childBox != multiColumnFlowThread() && childBox->isRenderMultiColumnSet()) { - RenderMultiColumnSet* multicolSet = toRenderMultiColumnSet(childBox); - if (multicolSet->recalculateBalancedHeight(firstPass)) { - multicolSet->setChildNeedsLayout(MarkOnlyThis); - needsRelayout = true; - } + for (RenderMultiColumnSet* multicolSet = multiColumnFlowThread()->firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) { + if (multicolSet->recalculateColumnHeight(firstPass)) + needsRelayout = true; + if (needsRelayout) { + // Once a column set gets a new column height, that column set and all successive column + // sets need to be laid out over again, since their logical top will be affected by + // this, and therefore their column heights may change as well, at least if the multicol + // height is constrained. + multicolSet->setChildNeedsLayout(MarkOnlyThis); } - + } if (needsRelayout) { // Layout again. Column balancing resulted in a new height. neededRelayout = true; @@ -3310,42 +3643,80 @@ bool RenderBlockFlow::relayoutForPagination(bool hasSpecifiedPageLogicalHeight, bool RenderBlockFlow::hasLines() const { - ASSERT(childrenInline()); + if (!childrenInline()) + return false; - if (m_simpleLineLayout) - return m_simpleLineLayout->lineCount(); + if (auto simpleLineLayout = this->simpleLineLayout()) + return simpleLineLayout->lineCount(); return lineBoxes().firstLineBox(); } -void RenderBlockFlow::layoutSimpleLines(LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom) +void RenderBlockFlow::invalidateLineLayoutPath() { - ASSERT(!m_lineBoxes.firstLineBox()); - - m_simpleLineLayout = SimpleLineLayout::create(*this); + switch (lineLayoutPath()) { + case UndeterminedPath: + case ForceLineBoxesPath: + ASSERT(!m_simpleLineLayout); + return; + case LineBoxesPath: + ASSERT(!m_simpleLineLayout); + setLineLayoutPath(UndeterminedPath); + return; + case SimpleLinesPath: + // The simple line layout may have become invalid. + m_simpleLineLayout = nullptr; + setLineLayoutPath(UndeterminedPath); + if (needsLayout()) + return; + // FIXME: We should just kick off a subtree layout here (if needed at all) see webkit.org/b/172947. + setNeedsLayout(); + return; + } + ASSERT_NOT_REACHED(); +} +void RenderBlockFlow::layoutSimpleLines(bool relayoutChildren, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom) +{ + bool needsLayout = selfNeedsLayout() || relayoutChildren || !m_simpleLineLayout; + if (needsLayout) { + deleteLineBoxesBeforeSimpleLineLayout(); + m_simpleLineLayout = SimpleLineLayout::create(*this); + } + if (view().layoutState() && view().layoutState()->isPaginated()) { + m_simpleLineLayout->setIsPaginated(); + SimpleLineLayout::adjustLinePositionsForPagination(*m_simpleLineLayout, *this); + } + for (auto& renderer : childrenOfType<RenderObject>(*this)) + renderer.clearNeedsLayout(); + ASSERT(!m_lineBoxes.firstLineBox()); LayoutUnit lineLayoutHeight = SimpleLineLayout::computeFlowHeight(*this, *m_simpleLineLayout); LayoutUnit lineLayoutTop = borderAndPaddingBefore(); - repaintLogicalTop = lineLayoutTop; - repaintLogicalBottom = lineLayoutTop + lineLayoutHeight; - + repaintLogicalBottom = needsLayout ? repaintLogicalTop + lineLayoutHeight : repaintLogicalTop; setLogicalHeight(lineLayoutTop + lineLayoutHeight + borderAndPaddingAfter()); } void RenderBlockFlow::deleteLineBoxesBeforeSimpleLineLayout() { - ASSERT(m_lineLayoutPath == SimpleLinesPath); + ASSERT(lineLayoutPath() == SimpleLinesPath); lineBoxes().deleteLineBoxes(); - toRenderText(firstChild())->deleteLineBoxesBeforeSimpleLineLayout(); + for (auto& renderer : childrenOfType<RenderObject>(*this)) { + if (is<RenderText>(renderer)) + downcast<RenderText>(renderer).deleteLineBoxesBeforeSimpleLineLayout(); + else if (is<RenderLineBreak>(renderer)) + downcast<RenderLineBreak>(renderer).deleteLineBoxesBeforeSimpleLineLayout(); + else + ASSERT_NOT_REACHED(); + } } void RenderBlockFlow::ensureLineBoxes() { - m_lineLayoutPath = ForceLineBoxesPath; - + setLineLayoutPath(ForceLineBoxesPath); if (!m_simpleLineLayout) return; + bool isPaginated = m_simpleLineLayout->isPaginated(); m_simpleLineLayout = nullptr; #if !ASSERT_DISABLED @@ -3356,7 +3727,15 @@ void RenderBlockFlow::ensureLineBoxes() bool relayoutChildren = false; LayoutUnit repaintLogicalTop; LayoutUnit repaintLogicalBottom; - layoutLineBoxes(relayoutChildren, repaintLogicalTop, repaintLogicalBottom); + if (isPaginated) { + view().pushLayoutStateForPagination(*this); + layoutLineBoxes(relayoutChildren, repaintLogicalTop, repaintLogicalBottom); + // This matches relayoutToAvoidWidows. + if (shouldBreakAtLineToAvoidWidow()) + layoutLineBoxes(relayoutChildren, repaintLogicalTop, repaintLogicalBottom); + view().popLayoutState(*this); + } else + layoutLineBoxes(relayoutChildren, repaintLogicalTop, repaintLogicalBottom); updateLogicalHeight(); ASSERT(didNeedLayout || logicalHeight() == oldHeight); @@ -3365,12 +3744,14 @@ void RenderBlockFlow::ensureLineBoxes() clearNeedsLayout(); } -#ifndef NDEBUG -void RenderBlockFlow::showLineTreeAndMark(const InlineBox* markedBox1, const char* markedLabel1, const InlineBox* markedBox2, const char* markedLabel2, const RenderObject* obj) const +#if ENABLE(TREE_DEBUGGING) +void RenderBlockFlow::showLineTreeAndMark(const InlineBox* markedBox, int depth) const { - RenderBlock::showLineTreeAndMark(markedBox1, markedLabel1, markedBox2, markedLabel2, obj); for (const RootInlineBox* root = firstRootBox(); root; root = root->nextRootBox()) - root->showLineTreeAndMark(markedBox1, markedLabel1, markedBox2, markedLabel2, obj, 1); + root->showLineTreeAndMark(markedBox, depth); + + if (auto simpleLineLayout = this->simpleLineLayout()) + SimpleLineLayout::showLineLayoutForFlow(*this, *simpleLineLayout, depth); } #endif @@ -3388,55 +3769,48 @@ void RenderBlockFlow::materializeRareBlockFlowData() m_rareBlockFlowData = std::make_unique<RenderBlockFlow::RenderBlockFlowRareData>(*this); } -#if ENABLE(IOS_TEXT_AUTOSIZING) -inline static bool isVisibleRenderText(RenderObject* renderer) +#if ENABLE(TEXT_AUTOSIZING) +static inline bool isVisibleRenderText(const RenderObject& renderer) { - if (!renderer->isText()) + if (!is<RenderText>(renderer)) return false; - RenderText* renderText = toRenderText(renderer); - return !renderText->linesBoundingBox().isEmpty() && !renderText->text()->containsOnlyWhitespace(); + + auto& renderText = downcast<RenderText>(renderer); + return !renderText.linesBoundingBox().isEmpty() && !renderText.text()->containsOnlyWhitespace(); } -inline static bool resizeTextPermitted(RenderObject* render) +static inline bool resizeTextPermitted(const RenderObject& renderer) { // We disallow resizing for text input fields and textarea to address <rdar://problem/5792987> and <rdar://problem/8021123> - auto renderer = render->parent(); - while (renderer) { + for (auto* ancestor = renderer.parent(); ancestor; ancestor = ancestor->parent()) { // Get the first non-shadow HTMLElement and see if it's an input. - if (renderer->element() && renderer->element()->isHTMLElement() && !renderer->element()->isInShadowTree()) { - const HTMLElement& element = toHTMLElement(*renderer->element()); - return !isHTMLInputElement(element) && !isHTMLTextAreaElement(element); + if (is<HTMLElement>(ancestor->element()) && !ancestor->element()->isInShadowTree()) { + auto& element = downcast<HTMLElement>(*ancestor->element()); + return !is<HTMLInputElement>(element) && !is<HTMLTextAreaElement>(element); } - renderer = renderer->parent(); } return true; } -int RenderBlockFlow::immediateLineCount() +int RenderBlockFlow::lineCountForTextAutosizing() { - // Copied and modified from RenderBlock::lineCount. + if (style().visibility() != VISIBLE) + return 0; + if (childrenInline()) + return lineCount(); // Only descend into list items. int count = 0; - if (style().visibility() == VISIBLE) { - if (childrenInline()) { - for (RootInlineBox* box = firstRootBox(); box; box = box->nextRootBox()) - count++; - } else { - for (RenderObject* obj = firstChild(); obj; obj = obj->nextSibling()) { - if (obj->isListItem()) - count += toRenderBlockFlow(obj)->lineCount(); - } - } - } + for (auto& listItem : childrenOfType<RenderListItem>(*this)) + count += listItem.lineCount(); return count; } -static bool isNonBlocksOrNonFixedHeightListItems(const RenderObject* render) +static bool isNonBlocksOrNonFixedHeightListItems(const RenderObject& renderer) { - if (!render->isRenderBlock()) + if (!renderer.isRenderBlock()) return true; - if (render->isListItem()) - return render->style().height().type() != Fixed; + if (renderer.isListItem()) + return renderer.style().height().type() != Fixed; return false; } @@ -3454,13 +3828,15 @@ static inline float textMultiplier(float specifiedSize) void RenderBlockFlow::adjustComputedFontSizes(float size, float visibleWidth) { + LOG(TextAutosizing, "RenderBlockFlow %p adjustComputedFontSizes, size=%f visibleWidth=%f, width()=%f. Bailing: %d", this, size, visibleWidth, width().toFloat(), visibleWidth >= width()); + // Don't do any work if the block is smaller than the visible area. if (visibleWidth >= width()) return; unsigned lineCount; if (m_lineCountForTextAutosizing == NOT_SET) { - int count = immediateLineCount(); + int count = lineCountForTextAutosizing(); if (!count) lineCount = NO_LINE; else if (count == 1) @@ -3477,60 +3853,60 @@ void RenderBlockFlow::adjustComputedFontSizes(float size, float visibleWidth) float actualWidth = m_widthForTextAutosizing != -1 ? static_cast<float>(m_widthForTextAutosizing) : static_cast<float>(width()); float scale = visibleWidth / actualWidth; float minFontSize = roundf(size / scale); - - for (RenderObject* descendent = traverseNext(this, isNonBlocksOrNonFixedHeightListItems); descendent; descendent = descendent->traverseNext(this, isNonBlocksOrNonFixedHeightListItems)) { - if (isVisibleRenderText(descendent) && resizeTextPermitted(descendent)) { - RenderText* text = toRenderText(descendent); - RenderStyle& oldStyle = text->style(); - FontDescription fontDescription = oldStyle.fontDescription(); - float specifiedSize = fontDescription.specifiedSize(); - float scaledSize = roundf(specifiedSize * scale); - if (scaledSize > 0 && scaledSize < minFontSize) { - // Record the width of the block and the line count the first time we resize text and use it from then on for text resizing. - // This makes text resizing consistent even if the block's width or line count changes (which can be caused by text resizing itself 5159915). - if (m_lineCountForTextAutosizing == NOT_SET) - m_lineCountForTextAutosizing = lineCount; - if (m_widthForTextAutosizing == -1) - m_widthForTextAutosizing = actualWidth; - - float candidateNewSize = 0; - float lineTextMultiplier = lineCount == ONE_LINE ? oneLineTextMultiplier(specifiedSize) : textMultiplier(specifiedSize); - candidateNewSize = roundf(std::min(minFontSize, specifiedSize * lineTextMultiplier)); - if (candidateNewSize > specifiedSize && candidateNewSize != fontDescription.computedSize() && text->textNode() && oldStyle.textSizeAdjust().isAuto()) - document().addAutoSizingNode(text->textNode(), candidateNewSize); - } + + for (auto* descendant = RenderObjectTraversal::firstChild(*this); descendant; ) { + if (!isNonBlocksOrNonFixedHeightListItems(*descendant)) { + descendant = RenderObjectTraversal::nextSkippingChildren(*descendant, this); + continue; + } + if (!isVisibleRenderText(*descendant) || !resizeTextPermitted(*descendant)) { + descendant = RenderObjectTraversal::next(*descendant, this); + continue; + } + + auto& text = downcast<RenderText>(*descendant); + auto& oldStyle = text.style(); + auto& fontDescription = oldStyle.fontDescription(); + float specifiedSize = fontDescription.specifiedSize(); + float scaledSize = roundf(specifiedSize * scale); + if (scaledSize > 0 && scaledSize < minFontSize) { + // Record the width of the block and the line count the first time we resize text and use it from then on for text resizing. + // This makes text resizing consistent even if the block's width or line count changes (which can be caused by text resizing itself 5159915). + if (m_lineCountForTextAutosizing == NOT_SET) + m_lineCountForTextAutosizing = lineCount; + if (m_widthForTextAutosizing == -1) + m_widthForTextAutosizing = actualWidth; + + float lineTextMultiplier = lineCount == ONE_LINE ? oneLineTextMultiplier(specifiedSize) : textMultiplier(specifiedSize); + float candidateNewSize = roundf(std::min(minFontSize, specifiedSize * lineTextMultiplier)); + if (candidateNewSize > specifiedSize && candidateNewSize != fontDescription.computedSize() && text.textNode() && oldStyle.textSizeAdjust().isAuto()) + document().addAutoSizedNode(*text.textNode(), candidateNewSize); } + + descendant = RenderObjectTraversal::nextSkippingChildren(text, this); } } -#endif // ENABLE(IOS_TEXT_AUTOSIZING) +#endif // ENABLE(TEXT_AUTOSIZING) RenderObject* RenderBlockFlow::layoutSpecialExcludedChild(bool relayoutChildren) { - if (!multiColumnFlowThread()) - return 0; + RenderMultiColumnFlowThread* flowThread = multiColumnFlowThread(); + if (!flowThread) + return nullptr; - // Update the dimensions of our regions before we lay out the flow thread. - // FIXME: Eventually this is going to get way more complicated, and we will be destroying regions - // instead of trying to keep them around. - bool shouldInvalidateRegions = false; - for (RenderBox* childBox = firstChildBox(); childBox; childBox = childBox->nextSiblingBox()) { - if (childBox == multiColumnFlowThread()) - continue; - - if (relayoutChildren || childBox->needsLayout()) { - if (!multiColumnFlowThread()->inBalancingPass() && childBox->isRenderMultiColumnSet()) - toRenderMultiColumnSet(childBox)->prepareForLayout(); - shouldInvalidateRegions = true; - } - } - - if (shouldInvalidateRegions) - multiColumnFlowThread()->invalidateRegions(); + setLogicalTopForChild(*flowThread, borderAndPaddingBefore()); if (relayoutChildren) - multiColumnFlowThread()->setChildNeedsLayout(MarkOnlyThis); - - if (multiColumnFlowThread()->requiresBalancing()) { + flowThread->setChildNeedsLayout(MarkOnlyThis); + + if (flowThread->needsLayout()) { + for (RenderMultiColumnSet* columnSet = flowThread->firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) + columnSet->prepareForLayout(!flowThread->inBalancingPass()); + + flowThread->invalidateRegions(MarkOnlyThis); + flowThread->setNeedsHeightsRecalculation(true); + flowThread->layout(); + } else { // At the end of multicol layout, relayoutForPagination() is called unconditionally, but if // no children are to be laid out (e.g. fixed width with layout already being up-to-date), // we want to prevent it from doing any work, so that the column balancing machinery doesn't @@ -3539,69 +3915,74 @@ RenderObject* RenderBlockFlow::layoutSpecialExcludedChild(bool relayoutChildren) // are actually required to guarantee this. The calculation of implicit breaks needs to be // preceded by a proper layout pass, since it's layout that sets up content runs, and the // runs get deleted right after every pass. - multiColumnFlowThread()->setNeedsRebalancing(shouldInvalidateRegions || multiColumnFlowThread()->needsLayout()); + flowThread->setNeedsHeightsRecalculation(false); } + determineLogicalLeftPositionForChild(*flowThread); - setLogicalTopForChild(*multiColumnFlowThread(), borderAndPaddingBefore()); - multiColumnFlowThread()->layoutIfNeeded(); - determineLogicalLeftPositionForChild(*multiColumnFlowThread()); - - return multiColumnFlowThread(); + return flowThread; } void RenderBlockFlow::addChild(RenderObject* newChild, RenderObject* beforeChild) { if (multiColumnFlowThread()) return multiColumnFlowThread()->addChild(newChild, beforeChild); - RenderBlock::addChild(newChild, beforeChild); + auto* beforeChildOrPlaceholder = beforeChild; + if (auto* containingFlowThread = flowThreadContainingBlock()) + beforeChildOrPlaceholder = containingFlowThread->resolveMovedChild(beforeChild); + RenderBlock::addChild(newChild, beforeChildOrPlaceholder); +} + +void RenderBlockFlow::removeChild(RenderObject& oldChild) +{ + if (!renderTreeBeingDestroyed()) { + RenderFlowThread* flowThread = multiColumnFlowThread(); + if (flowThread && flowThread != &oldChild) + flowThread->flowThreadRelativeWillBeRemoved(oldChild); + } + RenderBlock::removeChild(oldChild); } -void RenderBlockFlow::checkForPaginationLogicalHeightChange(LayoutUnit& pageLogicalHeight, bool& pageLogicalHeightChanged, bool& hasSpecifiedPageLogicalHeight) +void RenderBlockFlow::checkForPaginationLogicalHeightChange(bool& relayoutChildren, LayoutUnit& pageLogicalHeight, bool& pageLogicalHeightChanged) { - // If we don't use either of the two column implementations or a flow thread, then bail. - if (!isRenderFlowThread() && !multiColumnFlowThread() && !hasColumns()) + // If we don't use columns or flow threads, then bail. + if (!isRenderFlowThread() && !multiColumnFlowThread()) return; // We don't actually update any of the variables. We just subclassed to adjust our column height. - if (multiColumnFlowThread()) { - updateLogicalHeight(); - multiColumnFlowThread()->setColumnHeightAvailable(std::max<LayoutUnit>(contentLogicalHeight(), 0)); - setLogicalHeight(0); - } else if (hasColumns()) { - ColumnInfo* colInfo = columnInfo(); - - if (!pageLogicalHeight) { - // We need to go ahead and set our explicit page height if one exists, so that we can - // avoid doing two layout passes. - updateLogicalHeight(); - LayoutUnit columnHeight = isRenderView() ? view().pageOrViewLogicalHeight() : contentLogicalHeight(); - if (columnHeight > 0) { - pageLogicalHeight = columnHeight; - hasSpecifiedPageLogicalHeight = true; - } - setLogicalHeight(0); + if (RenderMultiColumnFlowThread* flowThread = multiColumnFlowThread()) { + LayoutUnit newColumnHeight; + if (hasDefiniteLogicalHeight() || view().frameView().pagination().mode != Pagination::Unpaginated) { + auto computedValues = computeLogicalHeight(LayoutUnit(), logicalTop()); + newColumnHeight = std::max<LayoutUnit>(computedValues.m_extent - borderAndPaddingLogicalHeight() - scrollbarLogicalHeight(), 0); + if (flowThread->columnHeightAvailable() != newColumnHeight) + relayoutChildren = true; } - - if (colInfo->columnHeight() != pageLogicalHeight && everHadLayout()) - pageLogicalHeightChanged = true; - - colInfo->setColumnHeight(pageLogicalHeight); - - if (!hasSpecifiedPageLogicalHeight && !pageLogicalHeight) - colInfo->clearForcedBreaks(); - - colInfo->setPaginationUnit(paginationUnit()); - } else if (isRenderFlowThread()) { - pageLogicalHeight = 1; // This is just a hack to always make sure we have a page logical height. - pageLogicalHeightChanged = toRenderFlowThread(this)->pageLogicalSizeChanged(); + flowThread->setColumnHeightAvailable(newColumnHeight); + } else if (is<RenderFlowThread>(*this)) { + RenderFlowThread& flowThread = downcast<RenderFlowThread>(*this); + + // FIXME: This is a hack to always make sure we have a page logical height, if said height + // is known. The page logical height thing in LayoutState is meaningless for flow + // thread-based pagination (page height isn't necessarily uniform throughout the flow + // thread), but as long as it is used universally as a means to determine whether page + // height is known or not, we need this. Page height is unknown when column balancing is + // enabled and flow thread height is still unknown (i.e. during the first layout pass). When + // it's unknown, we need to prevent the pagination code from assuming page breaks everywhere + // and thereby eating every top margin. It should be trivial to clean up and get rid of this + // hack once the old multicol implementation is gone (see also RenderView::pushLayoutStateForPagination). + pageLogicalHeight = flowThread.isPageLogicalHeightKnown() ? LayoutUnit(1) : LayoutUnit(0); + + pageLogicalHeightChanged = flowThread.pageLogicalSizeChanged(); } } +bool RenderBlockFlow::requiresColumns(int desiredColumnCount) const +{ + return willCreateColumns(desiredColumnCount); +} + void RenderBlockFlow::setComputedColumnCountAndWidth(int count, LayoutUnit width) { - if (!document().regionBasedColumnsEnabled()) - return RenderBlock::setComputedColumnCountAndWidth(count, width); - bool destroyColumns = !requiresColumns(count); if (destroyColumns) { if (multiColumnFlowThread()) @@ -3615,24 +3996,21 @@ void RenderBlockFlow::setComputedColumnCountAndWidth(int count, LayoutUnit width } } -void RenderBlockFlow::updateColumnProgressionFromStyle(RenderStyle* style) +void RenderBlockFlow::updateColumnProgressionFromStyle(RenderStyle& style) { - if (!document().regionBasedColumnsEnabled()) - return RenderBlock::updateColumnProgressionFromStyle(style); - if (!multiColumnFlowThread()) return; bool needsLayout = false; bool oldProgressionIsInline = multiColumnFlowThread()->progressionIsInline(); - bool newProgressionIsInline = style->hasInlineColumnAxis(); + bool newProgressionIsInline = style.hasInlineColumnAxis(); if (oldProgressionIsInline != newProgressionIsInline) { multiColumnFlowThread()->setProgressionIsInline(newProgressionIsInline); needsLayout = true; } bool oldProgressionIsReversed = multiColumnFlowThread()->progressionIsReversed(); - bool newProgressionIsReversed = style->columnProgression() == ReverseColumnProgression; + bool newProgressionIsReversed = style.columnProgression() == ReverseColumnProgression; if (oldProgressionIsReversed != newProgressionIsReversed) { multiColumnFlowThread()->setProgressionIsReversed(newProgressionIsReversed); needsLayout = true; @@ -3644,9 +4022,6 @@ void RenderBlockFlow::updateColumnProgressionFromStyle(RenderStyle* style) LayoutUnit RenderBlockFlow::computedColumnWidth() const { - if (!document().regionBasedColumnsEnabled()) - return RenderBlock::computedColumnWidth(); - if (multiColumnFlowThread()) return multiColumnFlowThread()->computedColumnWidth(); return contentLogicalWidth(); @@ -3654,9 +4029,6 @@ LayoutUnit RenderBlockFlow::computedColumnWidth() const unsigned RenderBlockFlow::computedColumnCount() const { - if (!document().regionBasedColumnsEnabled()) - return RenderBlock::computedColumnCount(); - if (multiColumnFlowThread()) return multiColumnFlowThread()->computedColumnCount(); @@ -3687,5 +4059,455 @@ bool RenderBlockFlow::isLeftLayoutOverflowAllowed() const return hasLeftOverflow; } +struct InlineMinMaxIterator { +/* InlineMinMaxIterator is a class that will iterate over all render objects that contribute to + inline min/max width calculations. Note the following about the way it walks: + (1) Positioned content is skipped (since it does not contribute to min/max width of a block) + (2) We do not drill into the children of floats or replaced elements, since you can't break + in the middle of such an element. + (3) Inline flows (e.g., <a>, <span>, <i>) are walked twice, since each side can have + distinct borders/margin/padding that contribute to the min/max width. +*/ + const RenderBlockFlow& parent; + RenderObject* current; + bool endOfInline; + bool initial; + + InlineMinMaxIterator(const RenderBlockFlow& p) + : parent(p) + , current(nullptr) + , endOfInline(false) + , initial(true) + { } + + RenderObject* next(); +}; + +RenderObject* InlineMinMaxIterator::next() +{ + RenderObject* result = nullptr; + bool oldEndOfInline = endOfInline; + endOfInline = false; + do { + if (!oldEndOfInline && (current && !current->isFloating() && !current->isReplaced() && !current->isOutOfFlowPositioned())) + result = current->firstChildSlow(); + else if (initial) { + result = parent.firstChild(); + initial = false; + } + + if (!result) { + // We hit the end of our inline. (It was empty, e.g., <span></span>.) + if (!oldEndOfInline && current && current->isRenderInline()) { + result = current; + endOfInline = true; + break; + } + + while (current && current != &parent) { + result = current->nextSibling(); + if (result) + break; + current = current->parent(); + if (current && current != &parent && current->isRenderInline()) { + result = current; + endOfInline = true; + break; + } + } + } + + if (!result) + break; + + if (!result->isOutOfFlowPositioned() && (result->isTextOrLineBreak() || result->isFloating() || result->isReplaced() || result->isRenderInline())) + break; + + current = result; + result = nullptr; + } while (current || current == &parent); + // Update our position. + current = result; + return result; +} + +static LayoutUnit getBPMWidth(LayoutUnit childValue, Length cssUnit) +{ + if (cssUnit.type() != Auto) + return (cssUnit.isFixed() ? LayoutUnit(cssUnit.value()) : childValue); + return 0; +} + +static LayoutUnit getBorderPaddingMargin(const RenderBoxModelObject& child, bool endOfInline) +{ + const RenderStyle& childStyle = child.style(); + if (endOfInline) { + return getBPMWidth(child.marginEnd(), childStyle.marginEnd()) + + getBPMWidth(child.paddingEnd(), childStyle.paddingEnd()) + + child.borderEnd(); + } + return getBPMWidth(child.marginStart(), childStyle.marginStart()) + + getBPMWidth(child.paddingStart(), childStyle.paddingStart()) + + child.borderStart(); +} + +static inline void stripTrailingSpace(float& inlineMax, float& inlineMin, RenderObject* trailingSpaceChild) +{ + if (is<RenderText>(trailingSpaceChild)) { + // Collapse away the trailing space at the end of a block. + RenderText& renderText = downcast<RenderText>(*trailingSpaceChild); + const UChar space = ' '; + const FontCascade& font = renderText.style().fontCascade(); // FIXME: This ignores first-line. + float spaceWidth = font.width(RenderBlock::constructTextRun(&space, 1, renderText.style())); + inlineMax -= spaceWidth + font.wordSpacing(); + if (inlineMin > inlineMax) + inlineMin = inlineMax; + } +} + +static inline LayoutUnit preferredWidth(LayoutUnit preferredWidth, float result) +{ + return std::max(preferredWidth, LayoutUnit::fromFloatCeil(result)); +} + +void RenderBlockFlow::computeInlinePreferredLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const +{ + float inlineMax = 0; + float inlineMin = 0; + + const RenderStyle& styleToUse = style(); + RenderBlock* containingBlock = this->containingBlock(); + LayoutUnit cw = containingBlock ? containingBlock->contentLogicalWidth() : LayoutUnit(); + + // If we are at the start of a line, we want to ignore all white-space. + // Also strip spaces if we previously had text that ended in a trailing space. + bool stripFrontSpaces = true; + RenderObject* trailingSpaceChild = nullptr; + + // Firefox and Opera will allow a table cell to grow to fit an image inside it under + // very specific cirucumstances (in order to match common WinIE renderings). + // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.) + bool allowImagesToBreak = !document().inQuirksMode() || !isTableCell() || !styleToUse.logicalWidth().isIntrinsicOrAuto(); + + bool oldAutoWrap = styleToUse.autoWrap(); + + InlineMinMaxIterator childIterator(*this); + + // Only gets added to the max preffered width once. + bool addedTextIndent = false; + // Signals the text indent was more negative than the min preferred width + bool hasRemainingNegativeTextIndent = false; + + LayoutUnit textIndent = minimumValueForLength(styleToUse.textIndent(), cw); + RenderObject* prevFloat = 0; + bool isPrevChildInlineFlow = false; + bool shouldBreakLineAfterText = false; + bool canHangPunctuationAtStart = styleToUse.hangingPunctuation() & FirstHangingPunctuation; + bool canHangPunctuationAtEnd = styleToUse.hangingPunctuation() & LastHangingPunctuation; + RenderText* lastText = nullptr; + + bool addedStartPunctuationHang = false; + + while (RenderObject* child = childIterator.next()) { + bool autoWrap = child->isReplaced() ? child->parent()->style().autoWrap() : + child->style().autoWrap(); + bool isAnonymousInlineBlock = child->isAnonymousInlineBlock(); + + if (!child->isBR()) { + // Step One: determine whether or not we need to terminate our current line. + // Each discrete chunk can become the new min-width, if it is the widest chunk + // seen so far, and it can also become the max-width. + + // Children fall into three categories: + // (1) An inline flow object. These objects always have a min/max of 0, + // and are included in the iteration solely so that their margins can + // be added in. + // + // (2) An inline non-text non-flow object, e.g., an inline replaced element. + // These objects can always be on a line by themselves, so in this situation + // we need to break the current line, and then add in our own margins and min/max + // width on its own line, and then terminate the line. + // + // (3) A text object. Text runs can have breakable characters at the start, + // the middle or the end. They may also lose whitespace off the front if + // we're already ignoring whitespace. In order to compute accurate min-width + // information, we need three pieces of information. + // (a) the min-width of the first non-breakable run. Should be 0 if the text string + // starts with whitespace. + // (b) the min-width of the last non-breakable run. Should be 0 if the text string + // ends with whitespace. + // (c) the min/max width of the string (trimmed for whitespace). + // + // If the text string starts with whitespace, then we need to terminate our current line + // (unless we're already in a whitespace stripping mode. + // + // If the text string has a breakable character in the middle, but didn't start + // with whitespace, then we add the width of the first non-breakable run and + // then end the current line. We then need to use the intermediate min/max width + // values (if any of them are larger than our current min/max). We then look at + // the width of the last non-breakable run and use that to start a new line + // (unless we end in whitespace). + const RenderStyle& childStyle = child->style(); + float childMin = 0; + float childMax = 0; + + if (!child->isText()) { + if (child->isLineBreakOpportunity()) { + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + inlineMin = 0; + continue; + } + // Case (1) and (2). Inline replaced and inline flow elements. + if (is<RenderInline>(*child)) { + // Add in padding/border/margin from the appropriate side of + // the element. + float bpm = getBorderPaddingMargin(downcast<RenderInline>(*child), childIterator.endOfInline); + childMin += bpm; + childMax += bpm; + + inlineMin += childMin; + inlineMax += childMax; + + child->setPreferredLogicalWidthsDirty(false); + } else { + // Inline replaced elts add in their margins to their min/max values. + if (!child->isFloating()) + lastText = nullptr; + LayoutUnit margins = 0; + Length startMargin = childStyle.marginStart(); + Length endMargin = childStyle.marginEnd(); + if (startMargin.isFixed()) + margins += LayoutUnit::fromFloatCeil(startMargin.value()); + if (endMargin.isFixed()) + margins += LayoutUnit::fromFloatCeil(endMargin.value()); + childMin += margins.ceilToFloat(); + childMax += margins.ceilToFloat(); + } + } + + if (!is<RenderInline>(*child) && !is<RenderText>(*child)) { + // Case (2). Inline replaced elements and floats. + // Terminate the current line as far as minwidth is concerned. + childMin += child->minPreferredLogicalWidth().ceilToFloat(); + childMax += child->maxPreferredLogicalWidth().ceilToFloat(); + + bool clearPreviousFloat; + if (child->isFloating()) { + clearPreviousFloat = (prevFloat + && ((prevFloat->style().floating() == LeftFloat && (childStyle.clear() & CLEFT)) + || (prevFloat->style().floating() == RightFloat && (childStyle.clear() & CRIGHT)))); + prevFloat = child; + } else + clearPreviousFloat = false; + + bool canBreakReplacedElement = !child->isImage() || allowImagesToBreak; + if (((canBreakReplacedElement && (autoWrap || oldAutoWrap) && (!isPrevChildInlineFlow || shouldBreakLineAfterText)) || clearPreviousFloat) || isAnonymousInlineBlock) { + if (child->isAnonymousInlineBlock() && styleToUse.collapseWhiteSpace()) + stripTrailingSpace(inlineMax, inlineMin, trailingSpaceChild); + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + inlineMin = 0; + } + + // If we're supposed to clear the previous float, then terminate maxwidth as well. + if (clearPreviousFloat || isAnonymousInlineBlock) { + maxLogicalWidth = preferredWidth(maxLogicalWidth, inlineMax); + inlineMax = 0; + } + + // Add in text-indent. This is added in only once. + if (!addedTextIndent && !child->isFloating() && !isAnonymousInlineBlock) { + LayoutUnit ceiledIndent = textIndent.ceilToFloat(); + childMin += ceiledIndent; + childMax += ceiledIndent; + + if (childMin < 0) + textIndent = LayoutUnit::fromFloatCeil(childMin); + else + addedTextIndent = true; + } + + if (canHangPunctuationAtStart && !addedStartPunctuationHang && !child->isFloating() && !isAnonymousInlineBlock) + addedStartPunctuationHang = true; + + // Add our width to the max. + inlineMax += std::max<float>(0, childMax); + + if ((!autoWrap || !canBreakReplacedElement || (isPrevChildInlineFlow && !shouldBreakLineAfterText)) && !isAnonymousInlineBlock) { + if (child->isFloating()) + minLogicalWidth = preferredWidth(minLogicalWidth, childMin); + else + inlineMin += childMin; + } else { + // Now check our line. + minLogicalWidth = preferredWidth(minLogicalWidth, childMin); + + // Now start a new line. + inlineMin = 0; + + if (child->isAnonymousInlineBlock()) { + // Terminate max width as well. + maxLogicalWidth = preferredWidth(maxLogicalWidth, childMax); + inlineMax = 0; + } + } + + if (autoWrap && canBreakReplacedElement && isPrevChildInlineFlow) { + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + inlineMin = 0; + } + + // We are no longer stripping whitespace at the start of a line. + if (!child->isFloating()) { + stripFrontSpaces = false; + trailingSpaceChild = nullptr; + lastText = nullptr; + } + } else if (is<RenderText>(*child)) { + // Case (3). Text. + RenderText& renderText = downcast<RenderText>(*child); + + if (renderText.style().hasTextCombine() && renderText.isCombineText()) + downcast<RenderCombineText>(renderText).combineText(); + + // Determine if we have a breakable character. Pass in + // whether or not we should ignore any spaces at the front + // of the string. If those are going to be stripped out, + // then they shouldn't be considered in the breakable char + // check. + bool hasBreakableChar, hasBreak; + float beginMin, endMin; + bool beginWS, endWS; + float beginMax, endMax; + bool strippingBeginWS = stripFrontSpaces; + renderText.trimmedPrefWidths(inlineMax, beginMin, beginWS, endMin, endWS, + hasBreakableChar, hasBreak, beginMax, endMax, + childMin, childMax, stripFrontSpaces); + + // This text object will not be rendered, but it may still provide a breaking opportunity. + if (!hasBreak && !childMax) { + if (autoWrap && (beginWS || endWS)) { + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + inlineMin = 0; + } + continue; + } + + lastText = &renderText; + + if (stripFrontSpaces) + trailingSpaceChild = child; + else + trailingSpaceChild = 0; + + // Add in text-indent. This is added in only once. + float ti = 0; + if (!addedTextIndent || hasRemainingNegativeTextIndent) { + ti = textIndent.ceilToFloat(); + childMin += ti; + beginMin += ti; + + // It the text indent negative and larger than the child minimum, we re-use the remainder + // in future minimum calculations, but using the negative value again on the maximum + // will lead to under-counting the max pref width. + if (!addedTextIndent) { + childMax += ti; + beginMax += ti; + addedTextIndent = true; + } + + if (childMin < 0) { + textIndent = childMin; + hasRemainingNegativeTextIndent = true; + } + } + + // See if we have a hanging punctuation situation at the start. + if (canHangPunctuationAtStart && !addedStartPunctuationHang) { + unsigned startIndex = strippingBeginWS ? renderText.firstCharacterIndexStrippingSpaces() : 0; + float hangStartWidth = renderText.hangablePunctuationStartWidth(startIndex); + childMin -= hangStartWidth; + beginMin -= hangStartWidth; + childMax -= hangStartWidth; + beginMax -= hangStartWidth; + addedStartPunctuationHang = true; + } + + // If we have no breakable characters at all, + // then this is the easy case. We add ourselves to the current + // min and max and continue. + if (!hasBreakableChar) + inlineMin += childMin; + else { + // We have a breakable character. Now we need to know if + // we start and end with whitespace. + if (beginWS) { + // End the current line. + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + } else { + inlineMin += beginMin; + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + childMin -= ti; + } + + inlineMin = childMin; + + if (endWS) { + // We end in whitespace, which means we can end our current line. + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + inlineMin = 0; + shouldBreakLineAfterText = false; + } else { + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + inlineMin = endMin; + shouldBreakLineAfterText = true; + } + } + + if (hasBreak) { + inlineMax += beginMax; + maxLogicalWidth = preferredWidth(maxLogicalWidth, inlineMax); + maxLogicalWidth = preferredWidth(maxLogicalWidth, childMax); + inlineMax = endMax; + addedTextIndent = true; + addedStartPunctuationHang = true; + } else + inlineMax += std::max<float>(0, childMax); + } + + // Ignore spaces after a list marker and also after an anonymous inline block. + if (child->isListMarker() || isAnonymousInlineBlock) + stripFrontSpaces = true; + } else { + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + maxLogicalWidth = preferredWidth(maxLogicalWidth, inlineMax); + inlineMin = inlineMax = 0; + stripFrontSpaces = true; + trailingSpaceChild = 0; + addedTextIndent = true; + addedStartPunctuationHang = true; + } + + if (!child->isText() && child->isRenderInline()) + isPrevChildInlineFlow = true; + else + isPrevChildInlineFlow = false; + + oldAutoWrap = autoWrap; + } + + if (styleToUse.collapseWhiteSpace()) + stripTrailingSpace(inlineMax, inlineMin, trailingSpaceChild); + + if (canHangPunctuationAtEnd && lastText && lastText->textLength() > 0) { + unsigned endIndex = trailingSpaceChild == lastText ? lastText->lastCharacterIndexStrippingSpaces() : lastText->textLength() - 1; + float endHangWidth = lastText->hangablePunctuationEndWidth(endIndex); + inlineMin -= endHangWidth; + inlineMax -= endHangWidth; + } + + minLogicalWidth = preferredWidth(minLogicalWidth, inlineMin); + maxLogicalWidth = preferredWidth(maxLogicalWidth, inlineMax); +} + } // namespace WebCore |