blob: f532eda6c4fe53b75066dad7cc7e9ea11f635e7b [file] [log] [blame]
/*
* Copyright (C) 2012 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS IN..0TERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/layout/LayoutMultiColumnFlowThread.h"
#include "core/layout/LayoutMultiColumnSet.h"
#include "core/layout/LayoutMultiColumnSpannerPlaceholder.h"
#include "core/layout/LayoutView.h"
#include "core/layout/MultiColumnFragmentainerGroup.h"
#include "core/layout/ViewFragmentationContext.h"
namespace blink {
#if DCHECK_IS_ON()
const LayoutBox* LayoutMultiColumnFlowThread::s_styleChangedBox;
#endif
bool LayoutMultiColumnFlowThread::s_couldContainSpanners;
bool LayoutMultiColumnFlowThread::s_toggleSpannersIfNeeded;
LayoutMultiColumnFlowThread::LayoutMultiColumnFlowThread()
: m_lastSetWorkedOn(nullptr),
m_columnCount(1),
m_columnHeightsChanged(false),
m_progressionIsInline(true),
m_isBeingEvacuated(false) {
setIsInsideFlowThread(true);
}
LayoutMultiColumnFlowThread::~LayoutMultiColumnFlowThread() {}
LayoutMultiColumnFlowThread* LayoutMultiColumnFlowThread::createAnonymous(
Document& document,
const ComputedStyle& parentStyle) {
LayoutMultiColumnFlowThread* layoutObject = new LayoutMultiColumnFlowThread();
layoutObject->setDocumentForAnonymous(&document);
layoutObject->setStyle(ComputedStyle::createAnonymousStyleWithDisplay(
parentStyle, EDisplay::Block));
return layoutObject;
}
LayoutMultiColumnSet* LayoutMultiColumnFlowThread::firstMultiColumnSet() const {
for (LayoutObject* sibling = nextSibling(); sibling;
sibling = sibling->nextSibling()) {
if (sibling->isLayoutMultiColumnSet())
return toLayoutMultiColumnSet(sibling);
}
return nullptr;
}
LayoutMultiColumnSet* LayoutMultiColumnFlowThread::lastMultiColumnSet() const {
for (LayoutObject* sibling = multiColumnBlockFlow()->lastChild(); sibling;
sibling = sibling->previousSibling()) {
if (sibling->isLayoutMultiColumnSet())
return toLayoutMultiColumnSet(sibling);
}
return nullptr;
}
static inline bool isMultiColumnContainer(const LayoutObject& object) {
if (!object.isLayoutBlockFlow())
return false;
return toLayoutBlockFlow(object).multiColumnFlowThread();
}
// Return true if there's nothing that prevents the specified object from being
// in the ancestor chain between some column spanner and its containing multicol
// container. A column spanner needs the multicol container to be its containing
// block, so that the spanner is able to escape the flow thread. (Everything
// contained by the flow thread is split into columns, but this is precisely
// what shouldn't be done to a spanner, since it's supposed to span all
// columns.)
//
// We require that the parent of the spanner participate in the block formatting
// context established by the multicol container (i.e. that there are no BFCs or
// other formatting contexts in-between). We also require that there be no
// transforms, since transforms insist on being in the containing block chain
// for everything inside it, which conflicts with a spanners's need to have the
// multicol container as its direct containing block. We may also not put
// spanners inside objects that don't support fragmentation.
static inline bool canContainSpannerInParentFragmentationContext(
const LayoutObject& object) {
if (!object.isLayoutBlockFlow())
return false;
const LayoutBlockFlow& blockFlow = toLayoutBlockFlow(object);
return !blockFlow.createsNewFormattingContext() &&
!blockFlow.hasTransformRelatedProperty() &&
blockFlow.getPaginationBreakability() != LayoutBox::ForbidBreaks &&
!isMultiColumnContainer(blockFlow);
}
static inline bool hasAnyColumnSpanners(
const LayoutMultiColumnFlowThread& flowThread) {
LayoutBox* firstBox = flowThread.firstMultiColumnBox();
return firstBox && (firstBox != flowThread.lastMultiColumnBox() ||
firstBox->isLayoutMultiColumnSpannerPlaceholder());
}
// Find the next layout object that has the multicol container in its containing
// block chain, skipping nested multicol containers.
static LayoutObject* nextInPreOrderAfterChildrenSkippingOutOfFlow(
LayoutMultiColumnFlowThread* flowThread,
LayoutObject* descendant) {
ASSERT(descendant->isDescendantOf(flowThread));
LayoutObject* object = descendant->nextInPreOrderAfterChildren(flowThread);
while (object) {
// Walk through the siblings and find the first one which is either in-flow
// or has this flow thread as its containing block flow thread.
if (!object->isOutOfFlowPositioned())
break;
if (object->containingBlock()->flowThreadContainingBlock() == flowThread) {
// This out-of-flow object is still part of the flow thread, because its
// containing block (probably relatively positioned) is part of the flow
// thread.
break;
}
object = object->nextInPreOrderAfterChildren(flowThread);
}
if (!object)
return nullptr;
#if ENABLE(ASSERT)
// Make sure that we didn't stumble into an inner multicol container.
for (LayoutObject* walker = object->parent(); walker && walker != flowThread;
walker = walker->parent())
ASSERT(!isMultiColumnContainer(*walker));
#endif
return object;
}
// Find the previous layout object that has the multicol container in its
// containing block chain, skipping nested multicol containers.
static LayoutObject* previousInPreOrderSkippingOutOfFlow(
LayoutMultiColumnFlowThread* flowThread,
LayoutObject* descendant) {
ASSERT(descendant->isDescendantOf(flowThread));
LayoutObject* object = descendant->previousInPreOrder(flowThread);
while (object && object != flowThread) {
if (object->isColumnSpanAll()) {
LayoutMultiColumnFlowThread* placeholderFlowThread =
toLayoutBox(object)->spannerPlaceholder()->flowThread();
if (placeholderFlowThread == flowThread)
break;
// We're inside an inner multicol container. We have no business there.
// Continue on the outside.
object = placeholderFlowThread->parent();
ASSERT(object->isDescendantOf(flowThread));
continue;
}
if (object->flowThreadContainingBlock() == flowThread) {
LayoutObject* ancestor;
for (ancestor = object->parent();; ancestor = ancestor->parent()) {
if (ancestor == flowThread)
return object;
if (isMultiColumnContainer(*ancestor)) {
// We're inside an inner multicol container. We have no business
// there.
break;
}
}
object = ancestor;
ASSERT(ancestor->isDescendantOf(flowThread));
continue; // Continue on the outside of the inner flow thread.
}
// We're inside something that's out-of-flow. Keep looking upwards and
// backwards in the tree.
object = object->previousInPreOrder(flowThread);
}
if (!object || object == flowThread)
return nullptr;
#if ENABLE(ASSERT)
// Make sure that we didn't stumble into an inner multicol container.
for (LayoutObject* walker = object->parent(); walker && walker != flowThread;
walker = walker->parent())
ASSERT(!isMultiColumnContainer(*walker));
#endif
return object;
}
static LayoutObject* firstLayoutObjectInSet(LayoutMultiColumnSet* multicolSet) {
LayoutBox* sibling = multicolSet->previousSiblingMultiColumnBox();
if (!sibling)
return multicolSet->flowThread()->firstChild();
// Adjacent column content sets should not occur. We would have no way of
// figuring out what each of them contains then.
ASSERT(sibling->isLayoutMultiColumnSpannerPlaceholder());
LayoutBox* spanner = toLayoutMultiColumnSpannerPlaceholder(sibling)
->layoutObjectInFlowThread();
return nextInPreOrderAfterChildrenSkippingOutOfFlow(
multicolSet->multiColumnFlowThread(), spanner);
}
static LayoutObject* lastLayoutObjectInSet(LayoutMultiColumnSet* multicolSet) {
LayoutBox* sibling = multicolSet->nextSiblingMultiColumnBox();
// By right we should return lastLeafChild() here, but the caller doesn't
// care, so just return nullptr.
if (!sibling)
return nullptr;
// Adjacent column content sets should not occur. We would have no way of
// figuring out what each of them contains then.
ASSERT(sibling->isLayoutMultiColumnSpannerPlaceholder());
LayoutBox* spanner = toLayoutMultiColumnSpannerPlaceholder(sibling)
->layoutObjectInFlowThread();
return previousInPreOrderSkippingOutOfFlow(
multicolSet->multiColumnFlowThread(), spanner);
}
LayoutMultiColumnSet* LayoutMultiColumnFlowThread::mapDescendantToColumnSet(
LayoutObject* layoutObject) const {
// Should not be used for spanners or content inside them.
DCHECK(!containingColumnSpannerPlaceholder(layoutObject));
DCHECK(layoutObject != this);
DCHECK(layoutObject->isDescendantOf(this));
// Out-of-flow objects don't belong in column sets.
DCHECK(layoutObject->containingBlock()->isDescendantOf(this));
DCHECK(layoutObject->flowThreadContainingBlock() == this);
DCHECK(!layoutObject->isLayoutMultiColumnSet());
DCHECK(!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;
}
LayoutMultiColumnSpannerPlaceholder*
LayoutMultiColumnFlowThread::containingColumnSpannerPlaceholder(
const LayoutObject* descendant) const {
ASSERT(descendant->isDescendantOf(this));
if (!hasAnyColumnSpanners(*this))
return nullptr;
// We have spanners. See if the layoutObject in question is one or inside of
// one then.
for (const LayoutObject* ancestor = descendant; ancestor && ancestor != this;
ancestor = ancestor->parent()) {
if (LayoutMultiColumnSpannerPlaceholder* placeholder =
ancestor->spannerPlaceholder())
return placeholder;
}
return nullptr;
}
void LayoutMultiColumnFlowThread::populate() {
LayoutBlockFlow* multicolContainer = multiColumnBlockFlow();
ASSERT(!nextSibling());
// Reparent children preceding the flow thread into the flow thread. It's
// multicol content now. At this point there's obviously nothing after the
// flow thread, but layoutObjects (column sets and spanners) will be inserted
// there as we insert elements into the flow thread.
multicolContainer->removeFloatingObjectsFromDescendants();
multicolContainer->moveChildrenTo(this, multicolContainer->firstChild(), this,
true);
}
void LayoutMultiColumnFlowThread::evacuateAndDestroy() {
LayoutBlockFlow* multicolContainer = multiColumnBlockFlow();
m_isBeingEvacuated = true;
// Remove all sets and spanners.
while (LayoutBox* columnBox = firstMultiColumnBox()) {
ASSERT(columnBox->isAnonymous());
columnBox->destroy();
}
ASSERT(!previousSibling());
ASSERT(!nextSibling());
// Finally we can promote all flow thread's children. Before we move them to
// the flow thread's container, we need to unregister the flow thread, so that
// they aren't just re-added again to the flow thread that we're trying to
// empty.
multicolContainer->resetMultiColumnFlowThread();
moveAllChildrenTo(multicolContainer, true);
// We used to manually nuke the line box tree here, but that should happen
// automatically when moving children around (the code above).
ASSERT(!firstLineBox());
destroy();
}
LayoutUnit LayoutMultiColumnFlowThread::maxColumnLogicalHeight() const {
if (m_columnHeightAvailable) {
// If height is non-auto, it's already constrained against max-height as
// well. Just return it.
return m_columnHeightAvailable;
}
const LayoutBlockFlow* multicolBlock = multiColumnBlockFlow();
Length logicalMaxHeight = multicolBlock->style()->logicalMaxHeight();
if (!logicalMaxHeight.isMaxSizeNone()) {
LayoutUnit resolvedLogicalMaxHeight =
multicolBlock->computeContentLogicalHeight(MaxSize, logicalMaxHeight,
LayoutUnit(-1));
if (resolvedLogicalMaxHeight != -1)
return resolvedLogicalMaxHeight;
}
return LayoutUnit::max();
}
LayoutUnit LayoutMultiColumnFlowThread::tallestUnbreakableLogicalHeight(
LayoutUnit offsetInFlowThread) const {
if (LayoutMultiColumnSet* multicolSet =
columnSetAtBlockOffset(offsetInFlowThread, AssociateWithLatterPage))
return multicolSet->tallestUnbreakableLogicalHeight();
return LayoutUnit();
}
LayoutSize LayoutMultiColumnFlowThread::columnOffset(
const LayoutPoint& point) const {
return flowThreadTranslationAtPoint(point,
CoordinateSpaceConversion::Containing);
}
bool LayoutMultiColumnFlowThread::needsNewWidth() const {
LayoutUnit newWidth;
unsigned dummyColumnCount; // We only care if used column-width changes.
calculateColumnCountAndWidth(newWidth, dummyColumnCount);
return newWidth != logicalWidth();
}
bool LayoutMultiColumnFlowThread::isPageLogicalHeightKnown() const {
if (LayoutMultiColumnSet* columnSet = lastMultiColumnSet())
return columnSet->isPageLogicalHeightKnown();
return false;
}
bool LayoutMultiColumnFlowThread::mayHaveNonUniformPageLogicalHeight() const {
const LayoutMultiColumnSet* columnSet = firstMultiColumnSet();
if (!columnSet)
return false;
if (columnSet->nextSiblingMultiColumnSet())
return true;
return enclosingFragmentationContext();
}
LayoutSize LayoutMultiColumnFlowThread::flowThreadTranslationAtOffset(
LayoutUnit offsetInFlowThread,
PageBoundaryRule rule,
CoordinateSpaceConversion mode) const {
if (!hasValidColumnSetInfo())
return LayoutSize(0, 0);
LayoutMultiColumnSet* columnSet =
columnSetAtBlockOffset(offsetInFlowThread, rule);
if (!columnSet)
return LayoutSize(0, 0);
return columnSet->flowThreadTranslationAtOffset(offsetInFlowThread, rule,
mode);
}
LayoutSize LayoutMultiColumnFlowThread::flowThreadTranslationAtPoint(
const LayoutPoint& flowThreadPoint,
CoordinateSpaceConversion mode) const {
LayoutPoint flippedPoint = flipForWritingMode(flowThreadPoint);
LayoutUnit blockOffset =
isHorizontalWritingMode() ? flippedPoint.y() : flippedPoint.x();
// If block direction is flipped, points at a column boundary belong in the
// former column, not the latter.
PageBoundaryRule rule = hasFlippedBlocksWritingMode()
? AssociateWithFormerPage
: AssociateWithLatterPage;
return flowThreadTranslationAtOffset(blockOffset, rule, mode);
}
LayoutPoint LayoutMultiColumnFlowThread::flowThreadPointToVisualPoint(
const LayoutPoint& flowThreadPoint) const {
return flowThreadPoint +
flowThreadTranslationAtPoint(flowThreadPoint,
CoordinateSpaceConversion::Visual);
}
LayoutPoint LayoutMultiColumnFlowThread::visualPointToFlowThreadPoint(
const LayoutPoint& visualPoint) const {
LayoutUnit blockOffset =
isHorizontalWritingMode() ? visualPoint.y() : visualPoint.x();
const LayoutMultiColumnSet* columnSet = nullptr;
for (const LayoutMultiColumnSet* candidate = firstMultiColumnSet(); candidate;
candidate = candidate->nextSiblingMultiColumnSet()) {
columnSet = candidate;
if (candidate->logicalBottom() > blockOffset)
break;
}
return columnSet
? columnSet->visualPointToFlowThreadPoint(toLayoutPoint(
visualPoint + location() - columnSet->location()))
: visualPoint;
}
int LayoutMultiColumnFlowThread::inlineBlockBaseline(
LineDirectionMode lineDirection) const {
LayoutUnit baselineInFlowThread =
LayoutUnit(LayoutFlowThread::inlineBlockBaseline(lineDirection));
LayoutMultiColumnSet* columnSet =
columnSetAtBlockOffset(baselineInFlowThread, AssociateWithLatterPage);
if (!columnSet)
return baselineInFlowThread.toInt();
return (baselineInFlowThread -
columnSet->pageLogicalTopForOffset(baselineInFlowThread))
.ceil();
}
LayoutMultiColumnSet* LayoutMultiColumnFlowThread::columnSetAtBlockOffset(
LayoutUnit offset,
PageBoundaryRule pageBoundaryRule) const {
LayoutMultiColumnSet* columnSet = m_lastSetWorkedOn;
if (columnSet) {
// Layout in progress. We are calculating the set heights as we speak, so
// the column set range information is not up to date.
while (columnSet->logicalTopInFlowThread() > offset) {
// Sometimes we have to use a previous set. This happens when we're
// working with a block that contains a spanner (so that there's a column
// set both before and after the spanner, and both sets contain said
// block).
LayoutMultiColumnSet* previousSet =
columnSet->previousSiblingMultiColumnSet();
if (!previousSet)
break;
columnSet = previousSet;
}
} else {
DCHECK(!m_columnSetsInvalidated);
if (m_multiColumnSetList.isEmpty())
return nullptr;
if (offset < LayoutUnit())
return m_multiColumnSetList.first();
MultiColumnSetSearchAdapter adapter(offset);
m_multiColumnSetIntervalTree
.allOverlapsWithAdapter<MultiColumnSetSearchAdapter>(adapter);
// If no set was found, the offset is in the flow thread overflow.
if (!adapter.result() && !m_multiColumnSetList.isEmpty())
return m_multiColumnSetList.last();
columnSet = adapter.result();
}
if (pageBoundaryRule == AssociateWithFormerPage && columnSet &&
offset == columnSet->logicalTopInFlowThread()) {
// The column set that we found starts at the exact same flow thread offset
// as we specified. Since we are to associate offsets at boundaries with the
// former fragmentainer, the fragmentainer we're looking for is in the
// previous column set.
if (LayoutMultiColumnSet* previousSet =
columnSet->previousSiblingMultiColumnSet())
return previousSet;
}
return columnSet;
}
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);
calculateColumnHeightAvailable();
if (FragmentationContext* enclosingFragmentationContext =
this->enclosingFragmentationContext()) {
m_blockOffsetInEnclosingFragmentationContext =
multiColumnBlockFlow()->offsetFromLogicalTopOfFirstPage();
m_blockOffsetInEnclosingFragmentationContext +=
multiColumnBlockFlow()->borderAndPaddingBefore();
if (LayoutMultiColumnFlowThread* enclosingFlowThread =
enclosingFragmentationContext->associatedFlowThread()) {
if (LayoutMultiColumnSet* firstSet = firstMultiColumnSet()) {
// Before we can start to lay out the contents of this multicol
// container, we need to make sure that all ancestor multicol containers
// have established a row to hold the first column contents of this
// container (this multicol container may start at the beginning of a
// new outer row). Without sufficient rows in all ancestor multicol
// containers, we may use the wrong column height.
LayoutUnit offset = m_blockOffsetInEnclosingFragmentationContext +
firstSet->logicalTopFromMulticolContentEdge();
enclosingFlowThread->appendNewFragmentainerGroupIfNeeded(
offset, AssociateWithLatterPage);
}
}
}
for (LayoutBox* columnBox = firstMultiColumnBox(); columnBox;
columnBox = columnBox->nextSiblingMultiColumnBox()) {
if (!columnBox->isLayoutMultiColumnSet()) {
// No other type is expected.
DCHECK(columnBox->isLayoutMultiColumnSpannerPlaceholder());
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();
}
void LayoutMultiColumnFlowThread::columnRuleStyleDidChange() {
for (LayoutMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet;
columnSet = columnSet->nextSiblingMultiColumnSet())
columnSet->setShouldDoFullPaintInvalidation(PaintInvalidationStyleChange);
}
bool LayoutMultiColumnFlowThread::removeSpannerPlaceholderIfNoLongerValid(
LayoutBox* spannerObjectInFlowThread) {
ASSERT(spannerObjectInFlowThread->spannerPlaceholder());
if (descendantIsValidColumnSpanner(spannerObjectInFlowThread))
return false; // Still a valid spanner.
// No longer a valid spanner. Get rid of the placeholder.
destroySpannerPlaceholder(spannerObjectInFlowThread->spannerPlaceholder());
ASSERT(!spannerObjectInFlowThread->spannerPlaceholder());
// We may have a new containing block, since we're no longer a spanner. Mark
// it for relayout.
spannerObjectInFlowThread->containingBlock()
->setNeedsLayoutAndPrefWidthsRecalc(
LayoutInvalidationReason::ColumnsChanged);
// Now generate a column set for this ex-spanner, if needed and none is there
// for us already.
flowThreadDescendantWasInserted(spannerObjectInFlowThread);
return true;
}
LayoutMultiColumnFlowThread* LayoutMultiColumnFlowThread::enclosingFlowThread()
const {
if (isLayoutPagedFlowThread()) {
// Paged overflow containers should never be fragmented by enclosing
// fragmentation contexts. They are to be treated as unbreakable content.
return nullptr;
}
if (multiColumnBlockFlow()->isInsideFlowThread())
return toLayoutMultiColumnFlowThread(
locateFlowThreadContainingBlockOf(*multiColumnBlockFlow()));
return nullptr;
}
FragmentationContext*
LayoutMultiColumnFlowThread::enclosingFragmentationContext() const {
if (LayoutMultiColumnFlowThread* enclosingFlowThread =
this->enclosingFlowThread())
return enclosingFlowThread;
return view()->fragmentationContext();
}
void LayoutMultiColumnFlowThread::appendNewFragmentainerGroupIfNeeded(
LayoutUnit offsetInFlowThread,
PageBoundaryRule pageBoundaryRule) {
LayoutMultiColumnSet* columnSet =
columnSetAtBlockOffset(offsetInFlowThread, pageBoundaryRule);
if (!columnSet->newFragmentainerGroupsAllowed())
return;
if (!columnSet->hasFragmentainerGroupForColumnAt(offsetInFlowThread,
pageBoundaryRule)) {
FragmentationContext* enclosingFragmentationContext =
this->enclosingFragmentationContext();
// Not nested. We'll never need more rows than the one we already have then.
if (!enclosingFragmentationContext)
return;
ASSERT(!isLayoutPagedFlowThread());
// We have run out of columns here, so we need to add at least one more row
// to hold more columns.
LayoutMultiColumnFlowThread* enclosingFlowThread =
enclosingFragmentationContext->associatedFlowThread();
do {
if (enclosingFlowThread) {
// When we add a new row here, 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. Need to insert
// additional rows in ancestral multicol containers before doing it in
// the descendants, in order to get the height constraints right down
// there.
const MultiColumnFragmentainerGroup& lastRow =
columnSet->lastFragmentainerGroup();
// The top offset where where the new fragmentainer group will start in
// this column set, converted to the coordinate space of the enclosing
// multicol container.
LayoutUnit logicalOffsetInOuter =
lastRow.blockOffsetInEnclosingFragmentationContext() +
lastRow.logicalHeight();
enclosingFlowThread->appendNewFragmentainerGroupIfNeeded(
logicalOffsetInOuter, AssociateWithLatterPage);
}
const MultiColumnFragmentainerGroup& newRow =
columnSet->appendNewFragmentainerGroup();
// Zero-height rows should really not occur here, but if it does anyway,
// break, so that we don't get stuck in an infinite loop.
ASSERT(newRow.logicalHeight() > 0);
if (newRow.logicalHeight() <= 0)
break;
} while (!columnSet->hasFragmentainerGroupForColumnAt(offsetInFlowThread,
pageBoundaryRule));
}
}
bool LayoutMultiColumnFlowThread::isFragmentainerLogicalHeightKnown() {
return isPageLogicalHeightKnown();
}
LayoutUnit LayoutMultiColumnFlowThread::fragmentainerLogicalHeightAt(
LayoutUnit blockOffset) {
return pageLogicalHeightForOffset(blockOffset);
}
LayoutUnit LayoutMultiColumnFlowThread::remainingLogicalHeightAt(
LayoutUnit blockOffset) {
return pageRemainingLogicalHeightForOffset(blockOffset,
AssociateWithLatterPage);
}
void LayoutMultiColumnFlowThread::calculateColumnHeightAvailable() {
// Calculate the non-auto content box height, or set it to 0 if it's auto. We
// need to know this before layout, so that we can figure out where to insert
// column breaks. We also treat LayoutView (which may be paginated, which uses
// the multicol implementation) as having a fixed height, since its height is
// deduced from the viewport height. We use computeLogicalHeight() to
// calculate the content box height. That method will clamp against max-height
// and min-height. Since we're now at the beginning of layout, and we don't
// know the actual height of the content yet, only call that method when
// height is definite, or we might fool ourselves into believing that columns
// have a definite height when they in fact don't.
LayoutBlockFlow* container = multiColumnBlockFlow();
LayoutUnit columnHeight;
if (container->hasDefiniteLogicalHeight() || container->isLayoutView()) {
LogicalExtentComputedValues computedValues;
container->computeLogicalHeight(LayoutUnit(), container->logicalTop(),
computedValues);
columnHeight = computedValues.m_extent -
container->borderAndPaddingLogicalHeight() -
container->scrollbarLogicalHeight();
}
setColumnHeightAvailable(std::max(columnHeight, LayoutUnit()));
}
void LayoutMultiColumnFlowThread::calculateColumnCountAndWidth(
LayoutUnit& width,
unsigned& count) const {
LayoutBlock* columnBlock = multiColumnBlockFlow();
const ComputedStyle* columnStyle = columnBlock->style();
LayoutUnit availableWidth = columnBlock->contentLogicalWidth();
LayoutUnit columnGap = LayoutUnit(columnBlock->columnGap());
LayoutUnit computedColumnWidth =
max(LayoutUnit(1), LayoutUnit(columnStyle->columnWidth()));
unsigned computedColumnCount = max<int>(1, columnStyle->columnCount());
ASSERT(!columnStyle->hasAutoColumnCount() ||
!columnStyle->hasAutoColumnWidth());
if (columnStyle->hasAutoColumnWidth() && !columnStyle->hasAutoColumnCount()) {
count = computedColumnCount;
width = ((availableWidth - ((count - 1) * columnGap)) / count)
.clampNegativeToZero();
} else if (!columnStyle->hasAutoColumnWidth() &&
columnStyle->hasAutoColumnCount()) {
count = std::max(LayoutUnit(1), (availableWidth + columnGap) /
(computedColumnWidth + columnGap))
.toUnsigned();
width = ((availableWidth + columnGap) / count) - columnGap;
} else {
count = std::max(std::min(LayoutUnit(computedColumnCount),
(availableWidth + columnGap) /
(computedColumnWidth + columnGap)),
LayoutUnit(1))
.toUnsigned();
width = ((availableWidth + columnGap) / count) - columnGap;
}
}
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());
}
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);
}
void LayoutMultiColumnFlowThread::destroySpannerPlaceholder(
LayoutMultiColumnSpannerPlaceholder* placeholder) {
if (LayoutBox* nextColumnBox = placeholder->nextSiblingMultiColumnBox()) {
LayoutBox* previousColumnBox = placeholder->previousSiblingMultiColumnBox();
if (nextColumnBox && nextColumnBox->isLayoutMultiColumnSet() &&
previousColumnBox && previousColumnBox->isLayoutMultiColumnSet()) {
// Need to merge two column sets.
nextColumnBox->destroy();
invalidateColumnSets();
}
}
placeholder->destroy();
}
bool LayoutMultiColumnFlowThread::descendantIsValidColumnSpanner(
LayoutObject* descendant) const {
// This method needs to behave correctly in the following situations:
// - When the descendant doesn't have a spanner placeholder but should have
// one (return true).
// - When the descendant doesn't have a spanner placeholder and still should
// not have one (return false).
// - When the descendant has a spanner placeholder but should no longer have
// one (return false).
// - When the descendant has a spanner placeholder and should still have one
// (return true).
// We assume that we're inside the flow thread. This function is not to be
// called otherwise.
ASSERT(descendant->isDescendantOf(this));
// The spec says that column-span only applies to in-flow block-level
// elements.
if (descendant->style()->getColumnSpan() != ColumnSpanAll ||
!descendant->isBox() || descendant->isInline() ||
descendant->isFloatingOrOutOfFlowPositioned())
return false;
if (!descendant->containingBlock()->isLayoutBlockFlow()) {
// Needs to be in a block-flow container, and not e.g. a table.
return false;
}
// This looks like a spanner, but if we're inside something unbreakable or
// something that establishes a new formatting context, it's not to be treated
// as one.
for (LayoutBox* ancestor = toLayoutBox(descendant)->parentBox(); ancestor;
ancestor = ancestor->containingBlock()) {
if (ancestor->isLayoutFlowThread()) {
ASSERT(ancestor == this);
return true;
}
if (!canContainSpannerInParentFragmentationContext(*ancestor))
return false;
}
ASSERT_NOT_REACHED();
return false;
}
void LayoutMultiColumnFlowThread::addColumnSetToThread(
LayoutMultiColumnSet* columnSet) {
if (LayoutMultiColumnSet* nextSet = columnSet->nextSiblingMultiColumnSet()) {
LayoutMultiColumnSetList::iterator it = m_multiColumnSetList.find(nextSet);
ASSERT(it != m_multiColumnSetList.end());
m_multiColumnSetList.insertBefore(it, columnSet);
} else {
m_multiColumnSetList.add(columnSet);
}
}
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::skipColumnSpanner(
LayoutBox* layoutObject,
LayoutUnit logicalTopInFlowThread) {
ASSERT(layoutObject->isColumnSpanAll());
LayoutMultiColumnSpannerPlaceholder* placeholder =
layoutObject->spannerPlaceholder();
LayoutBox* previousColumnBox = placeholder->previousSiblingMultiColumnBox();
if (previousColumnBox && previousColumnBox->isLayoutMultiColumnSet()) {
LayoutMultiColumnSet* columnSet = toLayoutMultiColumnSet(previousColumnBox);
// Negative margins may cause this.
if (logicalTopInFlowThread < columnSet->logicalTopInFlowThread())
logicalTopInFlowThread = columnSet->logicalTopInFlowThread();
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));
}
}
// When processing layout objects to remove or when processing layout objects
// that have just been inserted, certain types of objects should be skipped.
static bool shouldSkipInsertedOrRemovedChild(
LayoutMultiColumnFlowThread* flowThread,
const LayoutObject& child) {
if (child.isSVG() && !child.isSVGRoot()) {
// Don't descend into SVG objects. What's in there is of no interest, and
// there might even be a foreignObject there with column-span:all, which
// doesn't apply to us.
return true;
}
if (child.isLayoutFlowThread()) {
// Found an inner flow thread. We need to skip it and its descendants.
return true;
}
if (child.isLayoutMultiColumnSet() ||
child.isLayoutMultiColumnSpannerPlaceholder()) {
// Column sets and spanner placeholders in a child multicol context don't
// affect the parent flow thread.
return true;
}
if (child.isOutOfFlowPositioned() &&
child.containingBlock()->flowThreadContainingBlock() != flowThread) {
// Out-of-flow with its containing block on the outside of the multicol
// container.
return true;
}
return false;
}
void LayoutMultiColumnFlowThread::flowThreadDescendantWasInserted(
LayoutObject* descendant) {
ASSERT(!m_isBeingEvacuated);
// This method ensures that the list of column sets and spanner placeholders
// reflects the multicol content after having inserted a descendant (or
// descendant subtree). See the header file for more information. Go through
// the subtree that was just inserted and create column sets (needed by
// regular column content) and spanner placeholders (one needed by each
// spanner) where needed.
if (shouldSkipInsertedOrRemovedChild(this, *descendant))
return;
LayoutObject* objectAfterSubtree =
nextInPreOrderAfterChildrenSkippingOutOfFlow(this, descendant);
LayoutObject* next;
for (LayoutObject* layoutObject = descendant; layoutObject;
layoutObject = next) {
if (layoutObject != descendant &&
shouldSkipInsertedOrRemovedChild(this, *layoutObject)) {
next = layoutObject->nextInPreOrderAfterChildren(descendant);
continue;
}
next = layoutObject->nextInPreOrder(descendant);
if (containingColumnSpannerPlaceholder(layoutObject))
continue; // Inside a column spanner. Nothing to do, then.
if (descendantIsValidColumnSpanner(layoutObject)) {
// This layoutObject is a spanner, so it needs to establish a spanner
// placeholder.
createAndInsertSpannerPlaceholder(toLayoutBox(layoutObject),
objectAfterSubtree);
continue;
}
// This layoutObject is regular column content (i.e. not a spanner). Create
// a set if necessary.
if (objectAfterSubtree) {
if (LayoutMultiColumnSpannerPlaceholder* placeholder =
objectAfterSubtree->spannerPlaceholder()) {
// If inserted right before a spanner, we need to make sure that there's
// a set for us there.
LayoutBox* previous = placeholder->previousSiblingMultiColumnBox();
if (!previous || !previous->isLayoutMultiColumnSet())
createAndInsertMultiColumnSet(placeholder);
} else {
// Otherwise, since |objectAfterSubtree| isn't a spanner, it has to mean
// that there's already a set for that content. We can use it for this
// layoutObject too.
ASSERT(mapDescendantToColumnSet(objectAfterSubtree));
ASSERT(mapDescendantToColumnSet(layoutObject) ==
mapDescendantToColumnSet(objectAfterSubtree));
}
} else {
// Inserting at the end. Then we just need to make sure that there's a
// column set at the end.
LayoutBox* lastColumnBox = lastMultiColumnBox();
if (!lastColumnBox || !lastColumnBox->isLayoutMultiColumnSet())
createAndInsertMultiColumnSet();
}
}
}
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();
}
static inline bool needsToReinsertIntoFlowThread(
const ComputedStyle& oldStyle,
const ComputedStyle& newStyle) {
// If we've become (or are about to become) a container for absolutely
// positioned descendants, or if we're no longer going to be one, we need to
// re-evaluate the need for column sets. There may be out-of-flow descendants
// further down that become part of the flow thread, or cease to be part of
// the flow thread, because of this change.
if (oldStyle.hasTransformRelatedProperty() !=
newStyle.hasTransformRelatedProperty())
return true;
return (oldStyle.hasInFlowPosition() &&
newStyle.position() == StaticPosition) ||
(newStyle.hasInFlowPosition() &&
oldStyle.position() == StaticPosition);
}
static inline bool needsToRemoveFromFlowThread(const ComputedStyle& oldStyle,
const ComputedStyle& newStyle) {
// If an in-flow descendant goes out-of-flow, we may have to remove column
// sets and spanner placeholders.
return (newStyle.hasOutOfFlowPosition() &&
!oldStyle.hasOutOfFlowPosition()) ||
needsToReinsertIntoFlowThread(oldStyle, newStyle);
}
static inline bool needsToInsertIntoFlowThread(const ComputedStyle& oldStyle,
const ComputedStyle& newStyle) {
// If an out-of-flow descendant goes in-flow, we may have to insert column
// sets and spanner placeholders.
return (!newStyle.hasOutOfFlowPosition() &&
oldStyle.hasOutOfFlowPosition()) ||
needsToReinsertIntoFlowThread(oldStyle, newStyle);
}
void LayoutMultiColumnFlowThread::flowThreadDescendantStyleWillChange(
LayoutBox* descendant,
StyleDifference diff,
const ComputedStyle& newStyle) {
s_toggleSpannersIfNeeded = false;
if (needsToRemoveFromFlowThread(descendant->styleRef(), newStyle)) {
flowThreadDescendantWillBeRemoved(descendant);
return;
}
#if DCHECK_IS_ON()
s_styleChangedBox = descendant;
#endif
// Keep track of whether this object was of such a type that it could contain
// column-span:all descendants. If the style change in progress changes this
// state, we need to look for spanners to add or remove in the subtree of
// |descendant|.
s_toggleSpannersIfNeeded = true;
s_couldContainSpanners =
canContainSpannerInParentFragmentationContext(*descendant);
}
void LayoutMultiColumnFlowThread::flowThreadDescendantStyleDidChange(
LayoutBox* descendant,
StyleDifference diff,
const ComputedStyle& oldStyle) {
bool toggleSpannersIfNeeded = s_toggleSpannersIfNeeded;
s_toggleSpannersIfNeeded = false;
if (needsToInsertIntoFlowThread(oldStyle, descendant->styleRef())) {
flowThreadDescendantWasInserted(descendant);
return;
}
if (descendantIsValidColumnSpanner(descendant)) {
// We went from being regular column content to becoming a spanner.
ASSERT(!descendant->spannerPlaceholder());
// First remove this as regular column content. Note that this will walk the
// entire subtree of |descendant|. There might be spanners there (which
// won't be spanners anymore, since we're not allowed to nest spanners),
// whose placeholders must die.
flowThreadDescendantWillBeRemoved(descendant);
createAndInsertSpannerPlaceholder(
descendant,
nextInPreOrderAfterChildrenSkippingOutOfFlow(this, descendant));
return;
}
if (!toggleSpannersIfNeeded)
return;
#if DCHECK_IS_ON()
// Make sure that we were preceded by a call to
// flowThreadDescendantStyleWillChange() with the same descendant as we have
// now.
DCHECK(s_styleChangedBox == descendant);
#endif
if (s_couldContainSpanners !=
canContainSpannerInParentFragmentationContext(*descendant))
toggleSpannersInSubtree(descendant);
}
void LayoutMultiColumnFlowThread::toggleSpannersInSubtree(
LayoutBox* descendant) {
DCHECK(s_couldContainSpanners !=
canContainSpannerInParentFragmentationContext(*descendant));
// If there are no spanners at all in this multicol container, there's no
// need to look for any to remove.
if (s_couldContainSpanners && !hasAnyColumnSpanners(*this))
return;
bool walkChildren;
for (LayoutObject* object = descendant->nextInPreOrder(descendant); object;
object = walkChildren
? object->nextInPreOrder(descendant)
: object->nextInPreOrderAfterChildren(descendant)) {
walkChildren = false;
if (!object->isBox())
continue;
LayoutBox& box = toLayoutBox(*object);
if (s_couldContainSpanners) {
// Remove all spanners (turn them into regular column content), as we can
// no longer contain them.
if (box.isColumnSpanAll()) {
destroySpannerPlaceholder(box.spannerPlaceholder());
continue;
}
} else if (descendantIsValidColumnSpanner(object)) {
// We can now contain spanners, and we found a candidate. Turn it into a
// spanner, if it's not already one. We have to check if it's already a
// spanner, because in some cases we incorrectly think that we need to
// toggle spanners. One known case is when some ancestor changes
// writing-mode (which is an inherited property). Writing mode roots
// establish block formatting context (which means that there can be no
// column spanners inside). When changing the style on one object in the
// tree at a time, we're going to see writing mode roots that are not
// going to remain writing mode roots when all objects have been updated
// (because then all will have got the same writing mode).
if (!box.isColumnSpanAll()) {
createAndInsertSpannerPlaceholder(
&box, nextInPreOrderAfterChildrenSkippingOutOfFlow(this, &box));
}
continue;
}
walkChildren = canContainSpannerInParentFragmentationContext(box);
}
}
void LayoutMultiColumnFlowThread::computePreferredLogicalWidths() {
LayoutFlowThread::computePreferredLogicalWidths();
// 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.
const LayoutBlockFlow* multicolContainer = multiColumnBlockFlow();
const ComputedStyle* multicolStyle = multicolContainer->style();
int columnCount =
multicolStyle->hasAutoColumnCount() ? 1 : multicolStyle->columnCount();
LayoutUnit columnWidth;
LayoutUnit gapExtra =
LayoutUnit((columnCount - 1) * multicolContainer->columnGap());
if (multicolStyle->hasAutoColumnWidth()) {
m_minPreferredLogicalWidth =
m_minPreferredLogicalWidth * columnCount + gapExtra;
} else {
columnWidth = LayoutUnit(multicolStyle->columnWidth());
m_minPreferredLogicalWidth =
std::min(m_minPreferredLogicalWidth, columnWidth);
}
// Note that 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.
m_maxPreferredLogicalWidth =
std::max(m_maxPreferredLogicalWidth, columnWidth) * columnCount +
gapExtra;
}
void LayoutMultiColumnFlowThread::computeLogicalHeight(
LayoutUnit logicalHeight,
LayoutUnit logicalTop,
LogicalExtentComputedValues& computedValues) const {
// We simply remain at our intrinsic height.
computedValues.m_extent = logicalHeight;
computedValues.m_position = logicalTop;
}
void LayoutMultiColumnFlowThread::updateLogicalWidth() {
LayoutUnit columnWidth;
calculateColumnCountAndWidth(columnWidth, m_columnCount);
setLogicalWidth(columnWidth);
}
void LayoutMultiColumnFlowThread::layout() {
ASSERT(!m_lastSetWorkedOn);
m_lastSetWorkedOn = firstMultiColumnSet();
if (m_lastSetWorkedOn)
m_lastSetWorkedOn->beginFlow(LayoutUnit());
LayoutFlowThread::layout();
if (LayoutMultiColumnSet* lastSet = lastMultiColumnSet()) {
ASSERT(lastSet == m_lastSetWorkedOn);
if (!lastSet->nextSiblingMultiColumnBox()) {
// Include trailing overflow in the last column set. The idea is that we
// will generate additional columns and pages to hold that overflow, since
// people do write bad content like <body style="height:0px"> in
// multi-column layouts.
// TODO(mstensho): Once we support nested multicol, adding in overflow
// here may result in the need for creating additional rows, since there
// may not be enough space remaining in the currently last row.
LayoutRect layoutRect = layoutOverflowRect();
LayoutUnit logicalBottomInFlowThread =
isHorizontalWritingMode() ? layoutRect.maxY() : layoutRect.maxX();
ASSERT(logicalBottomInFlowThread >= logicalHeight());
lastSet->endFlow(logicalBottomInFlowThread);
}
}
m_lastSetWorkedOn = nullptr;
}
void LayoutMultiColumnFlowThread::contentWasLaidOut(
LayoutUnit logicalBottomInFlowThreadAfterPagination) {
// Check if we need another fragmentainer group. If we've run out of columns
// in the last fragmentainer group (column row), we need to insert another
// fragmentainer group to hold more columns.
// First figure out if there's any chance that we're nested at all. If we can
// be sure that we're not, bail early. This code is run very often, and since
// locating a containing flow thread has some cost (depending on tree depth),
// avoid calling enclosingFragmentationContext() right away. This test may
// give some false positives (hence the "mayBe"), if we're in an out-of-flow
// subtree and have an outer multicol container that doesn't affect us, but
// that's okay. We'll discover that further down the road when trying to
// locate our enclosing flow thread for real.
bool mayBeNested = multiColumnBlockFlow()->isInsideFlowThread() ||
view()->fragmentationContext();
if (!mayBeNested)
return;
appendNewFragmentainerGroupIfNeeded(logicalBottomInFlowThreadAfterPagination,
AssociateWithFormerPage);
}
bool LayoutMultiColumnFlowThread::canSkipLayout(const LayoutBox& root) const {
// Objects containing spanners is all we need to worry about, so if there are
// no spanners at all in this multicol container, we can just return the good
// news right away.
if (!hasAnyColumnSpanners(*this))
return true;
LayoutObject* next;
for (const LayoutObject* object = &root; object; object = next) {
if (object->isColumnSpanAll()) {
// A spanner potentially ends one fragmentainer group and begins a new
// one, and thus determines the flow thread portion bottom and top of
// adjacent fragmentainer groups. It's just too hard to guess these values
// without laying out.
return false;
}
if (canContainSpannerInParentFragmentationContext(*object))
next = object->nextInPreOrder(&root);
else
next = object->nextInPreOrderAfterChildren(&root);
}
return true;
}
MultiColumnLayoutState LayoutMultiColumnFlowThread::multiColumnLayoutState()
const {
return MultiColumnLayoutState(m_lastSetWorkedOn);
}
void LayoutMultiColumnFlowThread::restoreMultiColumnLayoutState(
const MultiColumnLayoutState& state) {
m_lastSetWorkedOn = state.columnSet();
}
} // namespace blink