RenderMultiColumnFlow.cpp [plain text]
#include "config.h"
#include "RenderMultiColumnFlow.h"
#include "HitTestResult.h"
#include "LayoutState.h"
#include "RenderIterator.h"
#include "RenderMultiColumnSet.h"
#include "RenderMultiColumnSpannerPlaceholder.h"
#include "RenderView.h"
#include "TransformState.h"
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMultiColumnFlow);
bool RenderMultiColumnFlow::gShiftingSpanner = false;
RenderMultiColumnFlow::RenderMultiColumnFlow(Document& document, RenderStyle&& style)
: RenderFragmentedFlow(document, WTFMove(style))
, m_spannerMap(std::make_unique<SpannerMap>())
, m_lastSetWorkedOn(nullptr)
, m_columnCount(1)
, m_columnWidth(0)
, m_columnHeightAvailable(0)
, m_inLayout(false)
, m_inBalancingPass(false)
, m_needsHeightsRecalculation(false)
, m_progressionIsInline(false)
, m_progressionIsReversed(false)
{
setFragmentedFlowState(InsideInFragmentedFlow);
}
RenderMultiColumnFlow::~RenderMultiColumnFlow() = default;
const char* RenderMultiColumnFlow::renderName() const
{
return "RenderMultiColumnFlowThread";
}
RenderMultiColumnSet* RenderMultiColumnFlow::firstMultiColumnSet() const
{
for (RenderObject* sibling = nextSibling(); sibling; sibling = sibling->nextSibling()) {
if (is<RenderMultiColumnSet>(*sibling))
return downcast<RenderMultiColumnSet>(sibling);
}
return nullptr;
}
RenderMultiColumnSet* RenderMultiColumnFlow::lastMultiColumnSet() const
{
for (RenderObject* sibling = multiColumnBlockFlow()->lastChild(); sibling; sibling = sibling->previousSibling()) {
if (is<RenderMultiColumnSet>(*sibling))
return downcast<RenderMultiColumnSet>(sibling);
}
return nullptr;
}
RenderBox* RenderMultiColumnFlow::firstColumnSetOrSpanner() const
{
if (RenderObject* sibling = nextSibling()) {
ASSERT(is<RenderBox>(*sibling));
ASSERT(is<RenderMultiColumnSet>(*sibling) || findColumnSpannerPlaceholder(downcast<RenderBox>(sibling)));
return downcast<RenderBox>(sibling);
}
return nullptr;
}
RenderBox* RenderMultiColumnFlow::nextColumnSetOrSpannerSiblingOf(const RenderBox* child)
{
if (!child)
return nullptr;
if (RenderObject* sibling = child->nextSibling())
return downcast<RenderBox>(sibling);
return nullptr;
}
RenderBox* RenderMultiColumnFlow::previousColumnSetOrSpannerSiblingOf(const RenderBox* child)
{
if (!child)
return nullptr;
if (RenderObject* sibling = child->previousSibling()) {
if (is<RenderFragmentedFlow>(*sibling))
return nullptr;
return downcast<RenderBox>(sibling);
}
return nullptr;
}
void RenderMultiColumnFlow::layout()
{
ASSERT(!m_inLayout);
m_inLayout = true;
m_lastSetWorkedOn = nullptr;
if (RenderBox* first = firstColumnSetOrSpanner()) {
if (is<RenderMultiColumnSet>(*first)) {
m_lastSetWorkedOn = downcast<RenderMultiColumnSet>(first);
m_lastSetWorkedOn->beginFlow(this);
}
}
RenderFragmentedFlow::layout();
if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) {
if (!nextColumnSetOrSpannerSiblingOf(lastSet))
lastSet->endFlow(this, logicalHeight());
lastSet->expandToEncompassFragmentedFlowContentsIfNeeded();
}
m_inLayout = false;
m_lastSetWorkedOn = nullptr;
}
static RenderMultiColumnSet* findSetRendering(const RenderMultiColumnFlow& fragmentedFlow, const RenderObject& renderer)
{
for (auto* multicolSet = fragmentedFlow.firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) {
if (multicolSet->containsRendererInFragmentedFlow(renderer))
return multicolSet;
}
return nullptr;
}
void RenderMultiColumnFlow::addFragmentToThread(RenderFragmentContainer* RenderFragmentContainer)
{
auto* columnSet = downcast<RenderMultiColumnSet>(RenderFragmentContainer);
if (RenderMultiColumnSet* nextSet = columnSet->nextSiblingMultiColumnSet()) {
RenderFragmentContainerList::iterator it = m_fragmentList.find(nextSet);
ASSERT(it != m_fragmentList.end());
m_fragmentList.insertBefore(it, columnSet);
} else
m_fragmentList.add(columnSet);
RenderFragmentContainer->setIsValid(true);
}
void RenderMultiColumnFlow::willBeRemovedFromTree()
{
for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet())
columnSet->detachFragment();
RenderFragmentedFlow::willBeRemovedFromTree();
}
RenderObject* RenderMultiColumnFlow::resolveMovedChild(RenderObject* child) const
{
if (!child)
return nullptr;
if (child->style().columnSpan() != ColumnSpanAll || !is<RenderBox>(*child)) {
return child;
}
if (RenderMultiColumnSpannerPlaceholder* placeholder = findColumnSpannerPlaceholder(downcast<RenderBox>(child)))
return placeholder;
return child;
}
static bool isValidColumnSpanner(const RenderMultiColumnFlow& fragmentedFlow, const RenderObject& descendant)
{
ASSERT(descendant.isDescendantOf(&fragmentedFlow));
if (!is<RenderBox>(descendant))
return false;
auto& descendantBox = downcast<RenderBox>(descendant);
if (descendantBox.isFloatingOrOutOfFlowPositioned())
return false;
if (descendantBox.style().columnSpan() != ColumnSpanAll)
return false;
auto* parent = descendantBox.parent();
if (!is<RenderBlockFlow>(*parent) || parent->childrenInline()) {
return false;
}
auto* enclosingFragmentedFlow = descendantBox.enclosingFragmentedFlow();
if (enclosingFragmentedFlow != &fragmentedFlow)
return false;
for (auto* ancestor = descendantBox.containingBlock(); ancestor; ancestor = ancestor->containingBlock()) {
if (is<RenderView>(*ancestor))
return false;
if (is<RenderFragmentedFlow>(*ancestor)) {
return ancestor == &fragmentedFlow;
}
if (is<RenderBlockFlow>(*ancestor) && downcast<RenderBlockFlow>(*ancestor).willCreateColumns())
return false;
ASSERT(ancestor->style().columnSpan() != ColumnSpanAll || !isValidColumnSpanner(fragmentedFlow, *ancestor));
if (ancestor->isUnsplittableForPagination())
return false;
}
ASSERT_NOT_REACHED();
return false;
}
static RenderObject* spannerPlacehoderCandidate(const RenderObject& renderer, const RenderMultiColumnFlow& stayWithin)
{
if (renderer.isOutOfFlowPositioned())
return nullptr;
ASSERT(renderer.isDescendantOf(&stayWithin));
auto* current = &renderer;
while (true) {
auto* nextSibling = current->nextSibling();
while (nextSibling && nextSibling->isOutOfFlowPositioned())
nextSibling = nextSibling->nextSibling();
if (nextSibling)
return nextSibling;
current = current->parent();
if (!current || current == &stayWithin || current->isOutOfFlowPositioned())
return nullptr;
}
return nullptr;
}
RenderObject* RenderMultiColumnFlow::processPossibleSpannerDescendant(RenderObject*& subtreeRoot, RenderObject& descendant)
{
RenderBlockFlow* multicolContainer = multiColumnBlockFlow();
RenderObject* nextRendererInFragmentedFlow = spannerPlacehoderCandidate(descendant, *this);
RenderObject* insertBeforeMulticolChild = nullptr;
RenderObject* nextDescendant = &descendant;
if (isValidColumnSpanner(*this, descendant)) {
RenderBlockFlow* container = downcast<RenderBlockFlow>(descendant.parent());
RenderMultiColumnSet* setToSplit = nullptr;
if (nextRendererInFragmentedFlow) {
setToSplit = findSetRendering(*this, descendant);
if (setToSplit) {
setToSplit->setNeedsLayout();
insertBeforeMulticolChild = setToSplit->nextSibling();
}
}
auto newPlaceholder = RenderMultiColumnSpannerPlaceholder::createAnonymous(*this, downcast<RenderBox>(descendant), container->style());
auto& placeholder = *newPlaceholder;
container->addChild(WTFMove(newPlaceholder), descendant.nextSibling());
auto takenDescendant = container->takeChild(descendant);
gShiftingSpanner = true;
multicolContainer->RenderBlock::addChild(WTFMove(takenDescendant), insertBeforeMulticolChild);
gShiftingSpanner = false;
if (subtreeRoot == &descendant)
subtreeRoot = &placeholder;
nextDescendant = &placeholder;
} else {
if (is<RenderMultiColumnSpannerPlaceholder>(nextRendererInFragmentedFlow)) {
RenderMultiColumnSpannerPlaceholder& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*nextRendererInFragmentedFlow);
if (RenderObject* previous = placeholder.spanner()->previousSibling()) {
if (is<RenderMultiColumnSet>(*previous))
return nextDescendant; }
insertBeforeMulticolChild = placeholder.spanner();
} else if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) {
if (!lastSet->nextSibling())
return nextDescendant;
}
}
auto newSet = createRenderer<RenderMultiColumnSet>(*this, RenderStyle::createAnonymousStyleWithDisplay(multicolContainer->style(), BLOCK));
newSet->initializeStyle();
auto& set = *newSet;
multicolContainer->RenderBlock::addChild(WTFMove(newSet), insertBeforeMulticolChild);
invalidateFragments();
ASSERT_UNUSED(set, !previousColumnSetOrSpannerSiblingOf(&set) || !previousColumnSetOrSpannerSiblingOf(&set)->isRenderMultiColumnSet());
ASSERT(!nextColumnSetOrSpannerSiblingOf(&set) || !nextColumnSetOrSpannerSiblingOf(&set)->isRenderMultiColumnSet());
return nextDescendant;
}
void RenderMultiColumnFlow::fragmentedFlowDescendantInserted(RenderObject& newDescendant)
{
if (gShiftingSpanner || newDescendant.isInFlowRenderFragmentedFlow())
return;
auto* subtreeRoot = &newDescendant;
auto* descendant = subtreeRoot;
while (descendant) {
if (is<RenderMultiColumnFlow>(*descendant)) {
descendant = descendant->nextSibling();
continue;
}
if (is<RenderMultiColumnSpannerPlaceholder>(*descendant)) {
RenderMultiColumnSpannerPlaceholder& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*descendant);
ASSERT(!spannerMap().get(placeholder.spanner()));
spannerMap().add(placeholder.spanner(), makeWeakPtr(downcast<RenderMultiColumnSpannerPlaceholder>(descendant)));
ASSERT(!placeholder.firstChild()); } else
descendant = processPossibleSpannerDescendant(subtreeRoot, *descendant);
if (descendant)
descendant = descendant->nextInPreOrder(subtreeRoot);
}
}
void RenderMultiColumnFlow::handleSpannerRemoval(RenderObject& spanner)
{
if (auto placeholder = spannerMap().take(&downcast<RenderBox>(spanner)))
placeholder->removeFromParentAndDestroy();
if (RenderObject* next = spanner.nextSibling()) {
if (RenderObject* previous = spanner.previousSibling()) {
if (previous->isRenderMultiColumnSet() && next->isRenderMultiColumnSet()) {
next->removeFromParentAndDestroy();
previous->setNeedsLayout();
}
}
}
}
void RenderMultiColumnFlow::fragmentedFlowRelativeWillBeRemoved(RenderObject& relative)
{
invalidateFragments();
if (is<RenderMultiColumnSpannerPlaceholder>(relative)) {
ASSERT(relative.isDescendantOf(this));
spannerMap().remove(downcast<RenderMultiColumnSpannerPlaceholder>(relative).spanner());
return;
}
if (relative.style().columnSpan() == ColumnSpanAll) {
if (relative.parent() != parent())
return;
handleSpannerRemoval(relative);
}
}
void RenderMultiColumnFlow::fragmentedFlowDescendantBoxLaidOut(RenderBox* descendant)
{
if (!is<RenderMultiColumnSpannerPlaceholder>(*descendant))
return;
auto& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*descendant);
RenderBlock* container = placeholder.containingBlock();
for (RenderBox* prev = previousColumnSetOrSpannerSiblingOf(placeholder.spanner()); prev; prev = previousColumnSetOrSpannerSiblingOf(prev)) {
if (is<RenderMultiColumnSet>(*prev)) {
downcast<RenderMultiColumnSet>(*prev).endFlow(container, placeholder.logicalTop());
break;
}
}
for (RenderBox* next = nextColumnSetOrSpannerSiblingOf(placeholder.spanner()); next; next = nextColumnSetOrSpannerSiblingOf(next)) {
if (is<RenderMultiColumnSet>(*next)) {
m_lastSetWorkedOn = downcast<RenderMultiColumnSet>(next);
m_lastSetWorkedOn->beginFlow(container);
break;
}
}
}
RenderBox::LogicalExtentComputedValues RenderMultiColumnFlow::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop) const
{
return { logicalHeight, logicalTop, ComputedMarginValues() };
}
LayoutUnit RenderMultiColumnFlow::initialLogicalWidth() const
{
return columnWidth();
}
void RenderMultiColumnFlow::setPageBreak(const RenderBlock* block, LayoutUnit offset, LayoutUnit spaceShortage)
{
if (auto* multicolSet = downcast<RenderMultiColumnSet>(fragmentAtBlockOffset(block, offset)))
multicolSet->recordSpaceShortage(spaceShortage);
}
void RenderMultiColumnFlow::updateMinimumPageHeight(const RenderBlock* block, LayoutUnit offset, LayoutUnit minHeight)
{
if (auto* multicolSet = downcast<RenderMultiColumnSet>(fragmentAtBlockOffset(block, offset)))
multicolSet->updateMinimumColumnHeight(minHeight);
}
RenderFragmentContainer* RenderMultiColumnFlow::fragmentAtBlockOffset(const RenderBox* box, LayoutUnit offset, bool extendLastFragment) const
{
if (!m_inLayout)
return RenderFragmentedFlow::fragmentAtBlockOffset(box, offset, extendLastFragment);
RenderMultiColumnSet* columnSet = m_lastSetWorkedOn ? m_lastSetWorkedOn : firstMultiColumnSet();
if (!columnSet) {
return nullptr;
}
if (offset < columnSet->logicalTopInFragmentedFlow()) {
do {
if (RenderMultiColumnSet* prev = columnSet->previousSiblingMultiColumnSet())
columnSet = prev;
else
break;
} while (offset < columnSet->logicalTopInFragmentedFlow());
} else {
while (offset >= columnSet->logicalBottomInFragmentedFlow()) {
RenderMultiColumnSet* next = columnSet->nextSiblingMultiColumnSet();
if (!next || !next->hasBeenFlowed())
break;
columnSet = next;
}
}
return columnSet;
}
void RenderMultiColumnFlow::setFragmentRangeForBox(const RenderBox& box, RenderFragmentContainer* startFragment, RenderFragmentContainer* endFragment)
{
for (RenderMultiColumnSet* columnSet = downcast<RenderMultiColumnSet>(*startFragment).previousSiblingMultiColumnSet(); columnSet; columnSet = columnSet->previousSiblingMultiColumnSet()) {
if (columnSet->logicalHeightInFragmentedFlow())
break;
startFragment = columnSet;
}
for (RenderMultiColumnSet* columnSet = downcast<RenderMultiColumnSet>(*startFragment).nextSiblingMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) {
if (columnSet->logicalHeightInFragmentedFlow())
break;
endFragment = columnSet;
}
RenderFragmentedFlow::setFragmentRangeForBox(box, startFragment, endFragment);
}
bool RenderMultiColumnFlow::addForcedFragmentBreak(const RenderBlock* block, LayoutUnit offset, RenderBox* , bool , LayoutUnit* offsetBreakAdjustment)
{
if (auto* multicolSet = downcast<RenderMultiColumnSet>(fragmentAtBlockOffset(block, offset))) {
multicolSet->addForcedBreak(offset);
if (offsetBreakAdjustment)
*offsetBreakAdjustment = pageLogicalHeightForOffset(offset) ? pageRemainingLogicalHeightForOffset(offset, IncludePageBoundary) : LayoutUnit::fromPixel(0);
return true;
}
return false;
}
LayoutSize RenderMultiColumnFlow::offsetFromContainer(RenderElement& enclosingContainer, const LayoutPoint& physicalPoint, bool* offsetDependsOnPoint) const
{
ASSERT(&enclosingContainer == container());
if (offsetDependsOnPoint)
*offsetDependsOnPoint = true;
LayoutPoint translatedPhysicalPoint(physicalPoint);
if (RenderFragmentContainer* fragment = physicalTranslationFromFlowToFragment(translatedPhysicalPoint))
translatedPhysicalPoint.moveBy(fragment->topLeftLocation());
LayoutSize offset(translatedPhysicalPoint.x(), translatedPhysicalPoint.y());
if (is<RenderBox>(enclosingContainer))
offset -= toLayoutSize(downcast<RenderBox>(enclosingContainer).scrollPosition());
return offset;
}
void RenderMultiColumnFlow::mapAbsoluteToLocalPoint(MapCoordinatesFlags mode, TransformState& transformState) const
{
parent()->mapAbsoluteToLocalPoint(mode, transformState);
LayoutPoint transformPoint(transformState.mappedPoint());
const RenderMultiColumnSet* candidateColumnSet = nullptr;
LayoutPoint candidatePoint;
LayoutSize candidateContainerOffset;
for (const auto& columnSet : childrenOfType<RenderMultiColumnSet>(*parent())) {
candidateContainerOffset = columnSet.offsetFromContainer(*parent(), LayoutPoint());
candidatePoint = transformPoint - candidateContainerOffset;
candidateColumnSet = &columnSet;
LayoutUnit pointOffset = isHorizontalWritingMode() ? candidatePoint.y() : candidatePoint.x();
LayoutUnit fragmentOffset = isHorizontalWritingMode() ? columnSet.topLeftLocation().y() : columnSet.topLeftLocation().x();
if (pointOffset < fragmentOffset + columnSet.logicalHeight())
break;
}
LayoutSize translationOffset = physicalTranslationFromFragmentToFlow(candidateColumnSet, candidatePoint) + candidateContainerOffset;
bool preserve3D = mode & UseTransforms && (parent()->style().preserves3D() || style().preserves3D());
if (mode & UseTransforms && shouldUseTransformFromContainer(parent())) {
TransformationMatrix t;
getTransformFromContainer(parent(), translationOffset, t);
transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform);
} else
transformState.move(translationOffset.width(), translationOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform);
}
LayoutSize RenderMultiColumnFlow::physicalTranslationFromFragmentToFlow(const RenderMultiColumnSet* columnSet, const LayoutPoint& physicalPoint) const
{
LayoutPoint logicalPoint = columnSet->flipForWritingMode(physicalPoint);
LayoutPoint translatedPoint = columnSet->translateFragmentPointToFragmentedFlow(logicalPoint);
LayoutPoint physicalTranslatedPoint = columnSet->flipForWritingMode(translatedPoint);
return physicalPoint - physicalTranslatedPoint;
}
RenderFragmentContainer* RenderMultiColumnFlow::mapFromFlowToFragment(TransformState& transformState) const
{
if (!hasValidFragmentInfo())
return nullptr;
LayoutRect boxRect = transformState.mappedQuad().enclosingBoundingBox();
flipForWritingMode(boxRect);
LayoutPoint centerPoint = boxRect.center();
LayoutUnit centerLogicalOffset = isHorizontalWritingMode() ? centerPoint.y() : centerPoint.x();
RenderFragmentContainer* RenderFragmentContainer = fragmentAtBlockOffset(this, centerLogicalOffset, true);
if (!RenderFragmentContainer)
return nullptr;
transformState.move(physicalTranslationOffsetFromFlowToFragment(RenderFragmentContainer, centerLogicalOffset));
return RenderFragmentContainer;
}
LayoutSize RenderMultiColumnFlow::physicalTranslationOffsetFromFlowToFragment(const RenderFragmentContainer* RenderFragmentContainer, const LayoutUnit logicalOffset) const
{
const auto* columnSet = downcast<RenderMultiColumnSet>(RenderFragmentContainer);
LayoutPoint translationOffset = columnSet->columnTranslationForOffset(logicalOffset);
if (style().isFlippedBlocksWritingMode()) {
LayoutRect portionRect(columnSet->fragmentedFlowPortionRect());
LayoutRect columnRect = columnSet->columnRectAt(0);
LayoutUnit physicalDeltaFromPortionBottom = logicalHeight() - columnSet->logicalBottomInFragmentedFlow();
if (isHorizontalWritingMode())
columnRect.setHeight(portionRect.height());
else
columnRect.setWidth(portionRect.width());
columnSet->flipForWritingMode(columnRect);
if (isHorizontalWritingMode())
translationOffset.move(0, columnRect.y() - portionRect.y() - physicalDeltaFromPortionBottom);
else
translationOffset.move(columnRect.x() - portionRect.x() - physicalDeltaFromPortionBottom, 0);
}
return LayoutSize(translationOffset.x(), translationOffset.y());
}
RenderFragmentContainer* RenderMultiColumnFlow::physicalTranslationFromFlowToFragment(LayoutPoint& physicalPoint) const
{
if (!hasValidFragmentInfo())
return nullptr;
LayoutPoint logicalPoint = flipForWritingMode(physicalPoint);
LayoutUnit logicalOffset = isHorizontalWritingMode() ? logicalPoint.y() : logicalPoint.x();
RenderFragmentContainer* RenderFragmentContainer = fragmentAtBlockOffset(this, logicalOffset, true);
if (!RenderFragmentContainer)
return nullptr;
LayoutSize translationOffset = physicalTranslationOffsetFromFlowToFragment(RenderFragmentContainer, logicalOffset);
physicalPoint += translationOffset;
return RenderFragmentContainer;
}
bool RenderMultiColumnFlow::isPageLogicalHeightKnown() const
{
if (RenderMultiColumnSet* columnSet = lastMultiColumnSet())
return columnSet->columnHeightComputed();
return false;
}
bool RenderMultiColumnFlow::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
{
if (hitTestAction == HitTestBlockBackground)
return false;
bool inside = RenderFragmentedFlow::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction);
if (inside && !result.innerNode())
return false;
return inside;
}
bool RenderMultiColumnFlow::shouldCheckColumnBreaks() const
{
if (!parent()->isRenderView())
return true;
return view().frameView().pagination().behavesLikeColumns;
}
}