void LayoutMultiColumnFlowThread::layoutColumns(SubtreeLayoutScope& layoutScope) { // Since we ended up here, it means that the multicol container (our parent) needed // layout. Since contents of the multicol container are diverted to the flow thread, the flow // thread needs layout as well. layoutScope.setChildNeedsLayout(this); m_blockOffsetInEnclosingFragmentationContext = enclosingFragmentationContext() ? multiColumnBlockFlow()->offsetFromLogicalTopOfFirstPage() : LayoutUnit(); for (LayoutBox* columnBox = firstMultiColumnBox(); columnBox; columnBox = columnBox->nextSiblingMultiColumnBox()) { if (!columnBox->isLayoutMultiColumnSet()) { ASSERT(columnBox->isLayoutMultiColumnSpannerPlaceholder()); // no other type is expected. continue; } LayoutMultiColumnSet* columnSet = toLayoutMultiColumnSet(columnBox); layoutScope.setChildNeedsLayout(columnSet); if (!m_columnHeightsChanged) { // This is the initial layout pass. We need to reset the column height, because contents // typically have changed. columnSet->resetColumnHeight(); } // Since column sets are regular block flow objects, and their position is changed in // regular block layout code (with no means for the multicol code to notice unless we add // hooks there), store the previous position now. If it changes in the imminent layout // pass, we may have to rebalance its columns. columnSet->storeOldPosition(); } m_columnHeightsChanged = false; invalidateColumnSets(); layout(); validateColumnSets(); }
LayoutMultiColumnSet* LayoutMultiColumnFlowThread::mapDescendantToColumnSet(LayoutObject* layoutObject) const { ASSERT(!containingColumnSpannerPlaceholder(layoutObject)); // should not be used for spanners or content inside them. ASSERT(layoutObject != this); ASSERT(layoutObject->isDescendantOf(this)); ASSERT(layoutObject->containingBlock()->isDescendantOf(this)); // Out-of-flow objects don't belong in column sets. ASSERT(layoutObject->flowThreadContainingBlock() == this); ASSERT(!layoutObject->isLayoutMultiColumnSet()); ASSERT(!layoutObject->isLayoutMultiColumnSpannerPlaceholder()); LayoutMultiColumnSet* multicolSet = firstMultiColumnSet(); if (!multicolSet) return nullptr; if (!multicolSet->nextSiblingMultiColumnSet()) return multicolSet; // This is potentially SLOW! But luckily very uncommon. You would have to dynamically insert a // spanner into the middle of column contents to need this. for (; multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) { LayoutObject* firstLayoutObject = firstLayoutObjectInSet(multicolSet); LayoutObject* lastLayoutObject = lastLayoutObjectInSet(multicolSet); ASSERT(firstLayoutObject); for (LayoutObject* walker = firstLayoutObject; walker; walker = walker->nextInPreOrder(this)) { if (walker == layoutObject) return multicolSet; if (walker == lastLayoutObject) break; } } return nullptr; }
void LayoutMultiColumnFlowThread::appendNewFragmentainerGroupIfNeeded(LayoutUnit offsetInFlowThread) { if (!isPageLogicalHeightKnown()) { // If we have no clue about the height of the multicol container, bail. This situation // occurs initially when an auto-height multicol container is nested inside another // auto-height multicol container. We need at least an estimated height of the outer // multicol container before we can check what an inner fragmentainer group has room for. // Its height is indefinite for now. return; } LayoutMultiColumnSet* columnSet = columnSetAtBlockOffset(offsetInFlowThread); if (columnSet->isInitialHeightCalculated()) { // We only insert additional fragmentainer groups in the initial layout pass. We only want // to balance columns in the last fragmentainer group (if we need to balance at all), so we // want that last fragmentainer group to be the same one in all layout passes that follow. return; } if (!columnSet->hasFragmentainerGroupForColumnAt(offsetInFlowThread)) { FragmentationContext* enclosingFragmentationContext = this->enclosingFragmentationContext(); if (!enclosingFragmentationContext) return; // Not nested. We'll never need more rows than the one we already have then. ASSERT(!isLayoutPagedFlowThread()); // We have run out of columns here, so we add another row to hold more columns. When we add // a new row, it implicitly means that we're inserting another column in our enclosing // multicol container. That in turn may mean that we've run out of columns there too. const MultiColumnFragmentainerGroup& newRow = columnSet->appendNewFragmentainerGroup(); if (LayoutMultiColumnFlowThread* enclosingFlowThread = enclosingFragmentationContext->associatedFlowThread()) enclosingFlowThread->appendNewFragmentainerGroupIfNeeded(newRow.blockOffsetInEnclosingFragmentationContext()); } }
void LayoutMultiColumnFlowThread::skipColumnSpanner(LayoutBox* layoutObject, LayoutUnit logicalTopInFlowThread) { ASSERT(layoutObject->isColumnSpanAll()); LayoutMultiColumnSpannerPlaceholder* placeholder = layoutObject->spannerPlaceholder(); LayoutBox* previousColumnBox = placeholder->previousSiblingMultiColumnBox(); if (previousColumnBox && previousColumnBox->isLayoutMultiColumnSet()) { LayoutMultiColumnSet* columnSet = toLayoutMultiColumnSet(previousColumnBox); if (logicalTopInFlowThread < columnSet->logicalTopInFlowThread()) logicalTopInFlowThread = columnSet->logicalTopInFlowThread(); // Negative margins may cause this. columnSet->endFlow(logicalTopInFlowThread); } LayoutBox* nextColumnBox = placeholder->nextSiblingMultiColumnBox(); if (nextColumnBox && nextColumnBox->isLayoutMultiColumnSet()) { LayoutMultiColumnSet* nextSet = toLayoutMultiColumnSet(nextColumnBox); m_lastSetWorkedOn = nextSet; nextSet->beginFlow(logicalTopInFlowThread); } // We'll lay out of spanners after flow thread layout has finished (during layout of the spanner // placeholders). There may be containing blocks for out-of-flow positioned descendants of the // spanner in the flow thread, so that out-of-flow objects inside the spanner will be laid out // as part of flow thread layout (even if the spanner itself won't). We need to add such // out-of-flow positioned objects to their containing blocks now, or they'll never get laid // out. Since it's non-trivial to determine if we need this, and where such out-of-flow objects // might be, just go through the whole subtree. for (LayoutObject* descendant = layoutObject->slowFirstChild(); descendant; descendant = descendant->nextInPreOrder()) { if (descendant->isBox() && descendant->isOutOfFlowPositioned()) descendant->containingBlock()->insertPositionedObject(toLayoutBox(descendant)); } }
void LayoutMultiColumnFlowThread::appendNewFragmentainerGroupIfNeeded(LayoutUnit bottomOffsetInFlowThread) { if (!isPageLogicalHeightKnown()) { // If we have no clue about the height of the multicol container, bail. This situation // occurs initially when an auto-height multicol container is nested inside another // auto-height multicol container. We need at least an estimated height of the outer // multicol container before we can check what an inner fragmentainer group has room for. // Its height is indefinite for now. return; } // TODO(mstensho): bottomOffsetInFlowThread is an endpoint-exclusive offset, i.e. the offset // just after the bottom of some object. So, ideally, columnSetAtBlockOffset() should be // informed about this (i.e. take a PageBoundaryRule argument). This is not the only place with // this issue; see also pageRemainingLogicalHeightForOffset(). LayoutMultiColumnSet* columnSet = columnSetAtBlockOffset(bottomOffsetInFlowThread); if (columnSet->isInitialHeightCalculated()) { // We only insert additional fragmentainer groups in the initial layout pass. We only want // to balance columns in the last fragmentainer group (if we need to balance at all), so we // want that last fragmentainer group to be the same one in all layout passes that follow. return; } if (!columnSet->hasFragmentainerGroupForColumnAt(bottomOffsetInFlowThread)) { FragmentationContext* enclosingFragmentationContext = this->enclosingFragmentationContext(); if (!enclosingFragmentationContext) return; // Not nested. We'll never need more rows than the one we already have then. ASSERT(!isLayoutPagedFlowThread()); // We have run out of columns here, so we add another row to hold more columns. When we add // a new row, it implicitly means that we're inserting another column in our enclosing // multicol container. That in turn may mean that we've run out of columns there too. const MultiColumnFragmentainerGroup& newRow = columnSet->appendNewFragmentainerGroup(); if (LayoutMultiColumnFlowThread* enclosingFlowThread = enclosingFragmentationContext->associatedFlowThread()) enclosingFlowThread->appendNewFragmentainerGroupIfNeeded(newRow.blockOffsetInEnclosingFragmentationContext() + newRow.logicalHeight()); } }
LayoutSize LayoutMultiColumnFlowThread::flowThreadTranslationAtOffset(LayoutUnit offsetInFlowThread) const { LayoutMultiColumnSet* columnSet = columnSetAtBlockOffset(offsetInFlowThread); if (!columnSet) return LayoutSize(0, 0); return columnSet->flowThreadTranslationAtOffset(offsetInFlowThread); }
LayoutMultiColumnSet* LayoutMultiColumnSet::createAnonymous(LayoutFlowThread& flowThread, const ComputedStyle& parentStyle) { Document& document = flowThread.document(); LayoutMultiColumnSet* layoutObject = new LayoutMultiColumnSet(&flowThread); layoutObject->setDocumentForAnonymous(&document); layoutObject->setStyle(ComputedStyle::createAnonymousStyleWithDisplay(parentStyle, BLOCK)); return layoutObject; }
LayoutUnit LayoutFlowThread::pageLogicalHeightForOffset(LayoutUnit offset) { LayoutMultiColumnSet* columnSet = columnSetAtBlockOffset(offset); if (!columnSet) return 0; return columnSet->pageLogicalHeight(); }
void LayoutFlowThread::collectLayerFragments(DeprecatedPaintLayerFragments& layerFragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) { ASSERT(!m_columnSetsInvalidated); for (LayoutMultiColumnSetList::const_iterator iter = m_multiColumnSetList.begin(); iter != m_multiColumnSetList.end(); ++iter) { LayoutMultiColumnSet* columnSet = *iter; columnSet->collectLayerFragments(layerFragments, layerBoundingBox, dirtyRect); } }
void LayoutFlowThread::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const { computedValues.m_position = logicalTop; computedValues.m_extent = 0; for (LayoutMultiColumnSetList::const_iterator iter = m_multiColumnSetList.begin(); iter != m_multiColumnSetList.end(); ++iter) { LayoutMultiColumnSet* columnSet = *iter; computedValues.m_extent += columnSet->logicalHeightInFlowThread(); } }
void LayoutMultiColumnFlowThread::willBeRemovedFromTree() { // Detach all column sets from the flow thread. Cannot destroy them at this point, since they // are siblings of this object, and there may be pointers to this object's sibling somewhere // further up on the call stack. for (LayoutMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) columnSet->detachFromFlowThread(); multiColumnBlockFlow()->resetMultiColumnFlowThread(); LayoutFlowThread::willBeRemovedFromTree(); }
void LayoutMultiColumnFlowThread::createAndInsertMultiColumnSet(LayoutBox* insertBefore) { LayoutBlockFlow* multicolContainer = multiColumnBlockFlow(); LayoutMultiColumnSet* newSet = LayoutMultiColumnSet::createAnonymous(*this, multicolContainer->styleRef()); multicolContainer->LayoutBlock::addChild(newSet, insertBefore); invalidateColumnSets(); // We cannot handle immediate column set siblings (and there's no need for it, either). // There has to be at least one spanner separating them. ASSERT(!newSet->previousSiblingMultiColumnBox() || !newSet->previousSiblingMultiColumnBox()->isLayoutMultiColumnSet()); ASSERT(!newSet->nextSiblingMultiColumnBox() || !newSet->nextSiblingMultiColumnBox()->isLayoutMultiColumnSet()); }
bool MultiColumnFragmentainerGroup::recalculateColumnHeight( LayoutMultiColumnSet& columnSet) { LayoutUnit oldColumnHeight = m_columnHeight; m_maxColumnHeight = calculateMaxColumnHeight(); // Only the last row may have auto height, and thus be balanced. There are no // good reasons to balance the preceding rows, and that could potentially lead // to an insane number of layout passes as well. if (isLastGroup() && columnSet.heightIsAuto()) { LayoutUnit newColumnHeight; if (!columnSet.isInitialHeightCalculated()) { // Initial balancing: Start with the lowest imaginable column height. Also // calculate the height of the tallest piece of unbreakable content. // Columns should never get any shorter than that (unless constrained by // max-height). Propagate this to our containing column set, in case there // is an outer multicol container that also needs to balance. After having // calculated the initial column height, the multicol container needs // another layout pass with the column height that we just calculated. InitialColumnHeightFinder initialHeightFinder( columnSet, logicalTopInFlowThread(), logicalBottomInFlowThread()); LayoutUnit tallestUnbreakableLogicalHeight = initialHeightFinder.tallestUnbreakableLogicalHeight(); columnSet.propagateTallestUnbreakableLogicalHeight( tallestUnbreakableLogicalHeight); newColumnHeight = std::max(initialHeightFinder.initialMinimalBalancedHeight(), tallestUnbreakableLogicalHeight); } else { // Rebalancing: After having laid out again, we'll need to rebalance if // the height wasn't enough and we're allowed to stretch it, and then // re-lay out. There are further details on the column balancing // machinery in ColumnBalancer and its derivates. newColumnHeight = rebalanceColumnHeightIfNeeded(); } setAndConstrainColumnHeight(newColumnHeight); } else { // The position of the column set may have changed, in which case height // available for columns may have changed as well. setAndConstrainColumnHeight(m_columnHeight); } if (m_columnHeight == oldColumnHeight) return false; // No change. We're done. return true; // Need another pass. }
LayoutUnit LayoutFlowThread::pageRemainingLogicalHeightForOffset(LayoutUnit offset, PageBoundaryRule pageBoundaryRule) { LayoutMultiColumnSet* columnSet = columnSetAtBlockOffset(offset); if (!columnSet) return 0; LayoutUnit pageLogicalTop = columnSet->pageLogicalTopForOffset(offset); LayoutUnit pageLogicalHeight = columnSet->pageLogicalHeight(); LayoutUnit pageLogicalBottom = pageLogicalTop + pageLogicalHeight; LayoutUnit remainingHeight = pageLogicalBottom - offset; if (pageBoundaryRule == IncludePageBoundary) { // If IncludePageBoundary is set, the line exactly on the top edge of a // columnSet will act as being part of the previous columnSet. remainingHeight = intMod(remainingHeight, pageLogicalHeight); } return remainingHeight; }
ColumnBalancer::ColumnBalancer(const LayoutMultiColumnSet& columnSet, LayoutUnit logicalTopInFlowThread, LayoutUnit logicalBottomInFlowThread) : m_columnSet(columnSet), m_logicalTopInFlowThread(logicalTopInFlowThread), m_logicalBottomInFlowThread(logicalBottomInFlowThread) { DCHECK_GE(columnSet.usedColumnCount(), 1U); }
void LayoutMultiColumnFlowThread::createAndInsertSpannerPlaceholder(LayoutBox* spannerObjectInFlowThread, LayoutObject* insertedBeforeInFlowThread) { LayoutBox* insertBeforeColumnBox = nullptr; LayoutMultiColumnSet* setToSplit = nullptr; if (insertedBeforeInFlowThread) { // The spanner is inserted before something. Figure out what this entails. If the // next object is a spanner too, it means that we can simply insert a new spanner // placeholder in front of its placeholder. insertBeforeColumnBox = insertedBeforeInFlowThread->spannerPlaceholder(); if (!insertBeforeColumnBox) { // The next object isn't a spanner; it's regular column content. Examine what // comes right before us in the flow thread, then. LayoutObject* previousLayoutObject = previousInPreOrderSkippingOutOfFlow(this, spannerObjectInFlowThread); if (!previousLayoutObject || previousLayoutObject == this) { // The spanner is inserted as the first child of the multicol container, // which means that we simply insert a new spanner placeholder at the // beginning. insertBeforeColumnBox = firstMultiColumnBox(); } else if (LayoutMultiColumnSpannerPlaceholder* previousPlaceholder = containingColumnSpannerPlaceholder(previousLayoutObject)) { // Before us is another spanner. We belong right after it then. insertBeforeColumnBox = previousPlaceholder->nextSiblingMultiColumnBox(); } else { // We're inside regular column content with both feet. Find out which column // set this is. It needs to be split it into two sets, so that we can insert // a new spanner placeholder between them. setToSplit = mapDescendantToColumnSet(previousLayoutObject); ASSERT(setToSplit == mapDescendantToColumnSet(insertedBeforeInFlowThread)); insertBeforeColumnBox = setToSplit->nextSiblingMultiColumnBox(); // We've found out which set that needs to be split. Now proceed to // inserting the spanner placeholder, and then insert a second column set. } } ASSERT(setToSplit || insertBeforeColumnBox); } LayoutBlockFlow* multicolContainer = multiColumnBlockFlow(); LayoutMultiColumnSpannerPlaceholder* newPlaceholder = LayoutMultiColumnSpannerPlaceholder::createAnonymous(multicolContainer->styleRef(), *spannerObjectInFlowThread); ASSERT(!insertBeforeColumnBox || insertBeforeColumnBox->parent() == multicolContainer); multicolContainer->LayoutBlock::addChild(newPlaceholder, insertBeforeColumnBox); spannerObjectInFlowThread->setSpannerPlaceholder(*newPlaceholder); if (setToSplit) createAndInsertMultiColumnSet(insertBeforeColumnBox); }
bool LayoutMultiColumnFlowThread::recalculateColumnHeights() { // All column sets that needed layout have now been laid out, so we can finally validate them. validateColumnSets(); if (!m_needsColumnHeightsRecalculation) return false; // Column heights may change here because of balancing. We may have to do multiple layout // passes, depending on how the contents is fitted to the changed column heights. In most // cases, laying out again twice or even just once will suffice. Sometimes we need more // passes than that, though, but the number of retries should not exceed the number of // columns, unless we have a bug. bool needsRelayout = false; for (LayoutMultiColumnSet* multicolSet = firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) needsRelayout |= multicolSet->recalculateColumnHeight(); if (needsRelayout) setChildNeedsLayout(MarkOnlyThis); m_inBalancingPass = needsRelayout; return needsRelayout; }
InitialColumnHeightFinder::InitialColumnHeightFinder( const LayoutMultiColumnSet& columnSet, LayoutUnit logicalTopInFlowThread, LayoutUnit logicalBottomInFlowThread) : ColumnBalancer(columnSet, logicalTopInFlowThread, logicalBottomInFlowThread) { m_shortestStruts.resize(columnSet.usedColumnCount()); for (auto& strut : m_shortestStruts) strut = LayoutUnit::max(); traverse(); // We have now found each explicit / forced break, and their location. Now we // need to figure out how many additional implicit / soft breaks we need and // guess where they will occur, in order // to provide an initial column height. distributeImplicitBreaks(); }
void LayoutMultiColumnFlowThread::flowThreadDescendantWillBeRemoved(LayoutObject* descendant) { // This method ensures that the list of column sets and spanner placeholders reflects the // multicol content that we'll be left with after removal of a descendant (or descendant // subtree). See the header file for more information. Removing content may mean that we need to // remove column sets and/or spanner placeholders. if (m_isBeingEvacuated) return; if (shouldSkipInsertedOrRemovedChild(this, *descendant)) return; bool hadContainingPlaceholder = containingColumnSpannerPlaceholder(descendant); bool processedSomething = false; LayoutObject* next; // Remove spanner placeholders that are no longer needed, and merge column sets around them. for (LayoutObject* layoutObject = descendant; layoutObject; layoutObject = next) { if (layoutObject != descendant && shouldSkipInsertedOrRemovedChild(this, *layoutObject)) { next = layoutObject->nextInPreOrderAfterChildren(descendant); continue; } processedSomething = true; LayoutMultiColumnSpannerPlaceholder* placeholder = layoutObject->spannerPlaceholder(); if (!placeholder) { next = layoutObject->nextInPreOrder(descendant); continue; } next = layoutObject->nextInPreOrderAfterChildren(descendant); // It's a spanner. Its children are of no interest to us. destroySpannerPlaceholder(placeholder); } if (hadContainingPlaceholder || !processedSomething) return; // No column content will be removed, so we can stop here. // Column content will be removed. Does this mean that we should destroy a column set? LayoutMultiColumnSpannerPlaceholder* adjacentPreviousSpannerPlaceholder = nullptr; LayoutObject* previousLayoutObject = previousInPreOrderSkippingOutOfFlow(this, descendant); if (previousLayoutObject && previousLayoutObject != this) { adjacentPreviousSpannerPlaceholder = containingColumnSpannerPlaceholder(previousLayoutObject); if (!adjacentPreviousSpannerPlaceholder) return; // Preceded by column content. Set still needed. } LayoutMultiColumnSpannerPlaceholder* adjacentNextSpannerPlaceholder = nullptr; LayoutObject* nextLayoutObject = nextInPreOrderAfterChildrenSkippingOutOfFlow(this, descendant); if (nextLayoutObject) { adjacentNextSpannerPlaceholder = containingColumnSpannerPlaceholder(nextLayoutObject); if (!adjacentNextSpannerPlaceholder) return; // Followed by column content. Set still needed. } // We have now determined that, with the removal of |descendant|, we should remove a column // set. Locate it and remove it. Do it without involving mapDescendantToColumnSet(), as that // might be very slow. Deduce the right set from the spanner placeholders that we've already // found. LayoutMultiColumnSet* columnSetToRemove; if (adjacentNextSpannerPlaceholder) { columnSetToRemove = toLayoutMultiColumnSet(adjacentNextSpannerPlaceholder->previousSiblingMultiColumnBox()); ASSERT(!adjacentPreviousSpannerPlaceholder || columnSetToRemove == adjacentPreviousSpannerPlaceholder->nextSiblingMultiColumnBox()); } else if (adjacentPreviousSpannerPlaceholder) { columnSetToRemove = toLayoutMultiColumnSet(adjacentPreviousSpannerPlaceholder->nextSiblingMultiColumnBox()); } else { // If there were no adjacent spanners, it has to mean that there's only one column set, // since it's only spanners that may cause creation of multiple sets. columnSetToRemove = firstMultiColumnSet(); ASSERT(columnSetToRemove); ASSERT(!columnSetToRemove->nextSiblingMultiColumnSet()); } ASSERT(columnSetToRemove); columnSetToRemove->destroy(); }
void LayoutMultiColumnFlowThread::columnRuleStyleDidChange() { for (LayoutMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) columnSet->setShouldDoFullPaintInvalidation(PaintInvalidationStyleChange); }