Chromium Code Reviews| Index: Source/core/rendering/RenderMultiColumnFlowThread.cpp |
| diff --git a/Source/core/rendering/RenderMultiColumnFlowThread.cpp b/Source/core/rendering/RenderMultiColumnFlowThread.cpp |
| index 8c406893d07571d1629c973e3d42e52db300086a..73cb131959cf0c7cde73ef9de37bce84220b017d 100644 |
| --- a/Source/core/rendering/RenderMultiColumnFlowThread.cpp |
| +++ b/Source/core/rendering/RenderMultiColumnFlowThread.cpp |
| @@ -27,15 +27,19 @@ |
| #include "core/rendering/RenderMultiColumnFlowThread.h" |
| #include "core/rendering/RenderMultiColumnSet.h" |
| +#include "core/rendering/RenderMultiColumnSpannerPlaceholder.h" |
| namespace WebCore { |
| RenderMultiColumnFlowThread::RenderMultiColumnFlowThread() |
| - : m_columnCount(1) |
| + : m_lastSetWorkedOn(0) |
| + , m_columnCount(1) |
| , m_columnHeightAvailable(0) |
| + , m_inLayout(false) |
| , m_inBalancingPass(false) |
| , m_needsColumnHeightsRecalculation(false) |
| , m_progressionIsInline(true) |
| + , m_beingEvacuated(false) |
| { |
| setFlowThreadState(InsideInFlowThread); |
| } |
| @@ -70,21 +74,47 @@ RenderMultiColumnSet* RenderMultiColumnFlowThread::lastMultiColumnSet() const |
| return 0; |
| } |
| -void RenderMultiColumnFlowThread::addChild(RenderObject* newChild, RenderObject* beforeChild) |
| +RenderBox* RenderMultiColumnFlowThread::firstColumnSetOrSpanner() const |
| { |
| - RenderBlockFlow::addChild(newChild, beforeChild); |
| - if (firstMultiColumnSet()) |
| - return; |
| + if (RenderObject* sibling = nextSibling()) { |
| + ASSERT(sibling->isBox()); |
| + ASSERT(sibling->isRenderMultiColumnSet() || findColumnSpannerPlaceholder(toRenderBox(sibling))); |
| + return toRenderBox(sibling); |
| + } |
| + return 0; |
| +} |
| - // For now we only create one column set. It's created as soon as the multicol container gets |
| - // any content at all. |
| - RenderMultiColumnSet* newSet = RenderMultiColumnSet::createAnonymous(this, multiColumnBlockFlow()->style()); |
| +RenderBox* RenderMultiColumnFlowThread::nextColumnSetOrSpannerSiblingOf(const RenderBox* child) |
| +{ |
| + if (!child) |
| + return 0; |
| + if (RenderObject* sibling = child->nextSibling()) { |
| + ASSERT(sibling->isBox()); |
| + return toRenderBox(sibling); |
| + } |
| + return 0; |
| +} |
| - // Need to skip RenderBlockFlow's implementation of addChild(), or we'd get redirected right |
| - // back here. |
| - multiColumnBlockFlow()->RenderBlock::addChild(newSet); |
| +RenderBox* RenderMultiColumnFlowThread::previousColumnSetOrSpannerSiblingOf(const RenderBox* child) |
| +{ |
| + if (!child) |
| + return 0; |
| + if (RenderObject* sibling = child->previousSibling()) { |
| + ASSERT(sibling->isBox()); |
| + if (sibling->isRenderFlowThread()) |
| + return 0; |
| + return toRenderBox(sibling); |
| + } |
| + return 0; |
| +} |
| - invalidateRegions(); |
| +RenderMultiColumnSet* RenderMultiColumnFlowThread::findSetRendering(RenderObject* renderer) const |
| +{ |
| + for (RenderMultiColumnSet* multicolSet = firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) { |
| + if (multicolSet->renders(renderer)) |
| + return multicolSet; |
| + } |
| + return 0; |
| } |
| void RenderMultiColumnFlowThread::populate() |
| @@ -100,20 +130,30 @@ void RenderMultiColumnFlowThread::populate() |
| void RenderMultiColumnFlowThread::evacuateAndDestroy() |
| { |
| RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); |
| + m_beingEvacuated = true; |
| - // Remove all sets. |
| - while (RenderMultiColumnSet* columnSet = firstMultiColumnSet()) |
| - columnSet->destroy(); |
| - |
| - ASSERT(!previousSibling()); |
| - ASSERT(!nextSibling()); |
| - |
| - // Finally we can promote all flow thread's children. Before we move them to the flow thread's |
| + // First promote all children of the flow thread. 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); |
| + // Move spanners back to their original DOM position in the tree, and destroy the placeholders. |
| + SpannerMap::iterator it; |
| + while ((it = m_spannerMap.begin()) != m_spannerMap.end()) { |
| + RenderBox* spanner = it->key; |
| + RenderMultiColumnSpannerPlaceholder* placeholder = it->value; |
| + RenderBlockFlow* originalContainer = toRenderBlockFlow(placeholder->parent()); |
| + multicolContainer->removeChild(spanner); |
| + originalContainer->addChild(spanner, placeholder); |
| + placeholder->destroy(); |
| + m_spannerMap.remove(it); |
| + } |
| + |
| + // Remove all sets. |
| + while (RenderMultiColumnSet* columnSet = firstMultiColumnSet()) |
| + columnSet->destroy(); |
| + |
| // FIXME: it's scary that neither destroy() nor the move*Children* methods take care of this, |
| // and instead leave you with dangling root line box pointers. But since this is how it is done |
| // in other parts of the code that deal with reparenting renderers, let's do the cleanup on our |
| @@ -167,10 +207,11 @@ void RenderMultiColumnFlowThread::layoutColumns(bool relayoutChildren, SubtreeLa |
| // typically have changed. |
| columnSet->resetColumnHeight(); |
| } |
| + columnSet->resetFlow(); |
| } |
| invalidateRegions(); |
| - m_needsColumnHeightsRecalculation = heightIsAuto(); |
| + m_needsColumnHeightsRecalculation = true; |
| layout(); |
| } |
| @@ -257,6 +298,210 @@ void RenderMultiColumnFlowThread::willBeRemovedFromTree() |
| RenderFlowThread::willBeRemovedFromTree(); |
| } |
| +RenderObject* RenderMultiColumnFlowThread::resolveMovedChild(RenderObject* child) const |
| +{ |
| + if (child->style()->columnSpan() != ColumnSpanAll || !child->isBox()) { |
| + // We only need to resolve for column spanners. |
| + return child; |
| + } |
| + // The renderer for the actual DOM node that establishes a spanner is moved from its original |
| + // location in the render tree to becoming a sibling of the column sets. In other words, it's |
| + // moved out from the flow thread (and becomes a sibling of it). When we for instance want to |
| + // create and insert a renderer for the sibling node immediately preceding the spanner, we need |
| + // to map that spanner renderer to the spanner's placeholder, which is where the new inserted |
| + // renderer belongs. |
| + if (RenderMultiColumnSpannerPlaceholder* placeholder = findColumnSpannerPlaceholder(toRenderBox(child))) |
| + return placeholder; |
| + |
| + // This is an invalid spanner, or its placeholder hasn't been created yet. This happens when |
| + // moving an entire subtree into the flow thread, when we are processing the insertion of this |
| + // spanner's preceding sibling, and we obviously haven't got as far as processing this spanner |
| + // yet. |
| + return child; |
| +} |
| + |
| +static bool isValidColumnSpanner(RenderMultiColumnFlowThread* flowThread, RenderObject* descendant) |
|
rune
2014/06/19 14:24:32
Why not make this a member method of the RenderMul
mstensho (USE GERRIT)
2014/08/26 09:43:58
Done.
rune
2014/08/27 08:17:38
Acknowledged.
|
| +{ |
| + // We assume that we're inside the flow thread. This function is not to be called otherwise. |
| + ASSERT(descendant->isDescendantOf(flowThread)); |
| + |
| + if (flowThread->isRenderPagedFlowThread()) |
| + return false; // Spanners are for true multicol only. |
| + |
| + // First make sure that the renderer itself has the right properties for becoming a spanner. |
| + RenderStyle* style = descendant->style(); |
| + if (style->columnSpan() != ColumnSpanAll || !descendant->isBox() || descendant->isFloatingOrOutOfFlowPositioned()) |
| + return false; |
| + |
| + RenderBlock* container = descendant->containingBlock(); |
| + if (!container->isRenderBlockFlow() || container->childrenInline()) { |
| + // Needs to be block-level. |
| + return false; |
| + } |
| + |
| + // This looks like a spanner, but if we're inside something unbreakable, it's not to be treated as one. |
| + for (RenderBox* ancestor = toRenderBox(descendant)->parentBox(); ancestor; ancestor = ancestor->parentBox()) { |
| + if (ancestor->isRenderFlowThread()) { |
| + // Don't allow any intervening non-multicol fragmentation contexts. The spec doesn't say |
| + // anything about disallowing this, but it's just going to be too complicated to |
| + // implement (not to mention specify behavior). |
| + return ancestor == flowThread; |
| + } |
| + ASSERT(ancestor->style()->columnSpan() != ColumnSpanAll || !isValidColumnSpanner(flowThread, ancestor)); |
| + if (ancestor->isUnsplittableForPagination()) |
| + return false; |
| + } |
| + ASSERT_NOT_REACHED(); |
| + return false; |
| +} |
| + |
| +void RenderMultiColumnFlowThread::flowThreadDescendantInserted(RenderObject* descendant) |
| +{ |
| + if (m_beingEvacuated) |
| + return; |
| + RenderObject* subtreeRoot = descendant; |
| + for (; descendant; descendant = descendant->nextInPreOrder(subtreeRoot)) { |
| + if (descendant->isRenderMultiColumnSpannerPlaceholder()) { |
| + // A spanner's placeholder has been inserted. The actual spanner renderer is moved from |
| + // where it wound otherwise occur (if it weren't a spanner) to becoming a sibling of the |
|
rune
2014/06/19 14:24:33
"where it _would_ otherwise ..."
mstensho (USE GERRIT)
2014/08/26 09:43:58
Done.
rune
2014/08/27 08:17:38
Acknowledged.
|
| + // column sets. |
| + RenderMultiColumnSpannerPlaceholder* placeholder = toRenderMultiColumnSpannerPlaceholder(descendant); |
| + ASSERT(!m_spannerMap.get(placeholder->spanner())); |
| + m_spannerMap.add(placeholder->spanner(), placeholder); |
| + ASSERT(!placeholder->slowFirstChild()); // There should be no children here, but if there are, we ought to skip them. |
| + continue; |
| + } |
| + RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); |
| + RenderObject* nextRendererInFlowThread = descendant->nextInPreOrderAfterChildren(this); |
| + RenderObject* insertBeforeMulticolChild = 0; |
| + if (isValidColumnSpanner(this, descendant)) { |
| + // This is a spanner (column-span:all). Such renderers are moved from where they would |
| + // otherwise occur in the render tree to becoming a direct child of the multicol container, |
| + // so that they live among the column sets. This simplifies the layout implementation, and |
| + // basically just relies on regular block layout done by the RenderBlockFlow that |
| + // establishes the multicol container. |
| + RenderBlockFlow* container = toRenderBlockFlow(descendant->parent()); |
| + RenderMultiColumnSet* setToSplit = 0; |
| + if (nextRendererInFlowThread) { |
| + setToSplit = findSetRendering(descendant); |
| + if (setToSplit) { |
| + setToSplit->setNeedsLayoutAndFullRepaint(); |
| + insertBeforeMulticolChild = setToSplit->nextSibling(); |
| + } |
| + } |
| + // Moving a spanner's renderer so that it becomes a sibling of the column sets requires us |
| + // to insert an anonymous placeholder in the tree where the spanner's renderer otherwise |
| + // would have been. This is needed for a two reasons: We need a way of separating inline |
|
rune
2014/06/19 14:24:32
"needed for a two reasons"?
mstensho (USE GERRIT)
2014/08/26 09:43:58
Done.
rune
2014/08/27 08:17:38
Acknowledged.
|
| + // content before and after the spanner, so that it becomes separate line boxes. Secondly, |
| + // this placeholder serves as a break point for column sets, so that, when encountered, we |
| + // end flowing one column set and move to the next one. |
|
rune
2014/06/19 14:24:33
"move _on_ to the next one" perhaps?
mstensho (USE GERRIT)
2014/08/26 09:43:58
Done.
rune
2014/08/27 08:17:38
Acknowledged.
|
| + RenderMultiColumnSpannerPlaceholder* placeholder = RenderMultiColumnSpannerPlaceholder::createAnonymous(this, toRenderBox(descendant), container->style()); |
| + container->addChild(placeholder, descendant->nextSibling()); |
| + container->removeChild(descendant); |
| + multicolContainer->RenderBlock::addChild(descendant, insertBeforeMulticolChild); |
| + |
| + // The spanner has now been moved out from the flow thread, but we don't want to |
| + // examine its children anyway. They are all part of the spanner and shouldn't trigger |
| + // creation of column sets or anything like that. Continue at its original position in |
| + // the tree, i.e. where the placeholder was just put. |
| + if (subtreeRoot == descendant) |
| + subtreeRoot = placeholder; |
| + descendant = placeholder; |
| + } else { |
| + // This is regular multicol content, i.e. not part of a spanner. |
| + if (nextRendererInFlowThread && nextRendererInFlowThread->isRenderMultiColumnSpannerPlaceholder()) { |
| + // Inserted right before a spanner. Is there a set for us there? |
| + RenderMultiColumnSpannerPlaceholder* placeholder = toRenderMultiColumnSpannerPlaceholder(nextRendererInFlowThread); |
| + if (RenderObject* previous = placeholder->spanner()->previousSibling()) { |
| + if (previous->isRenderMultiColumnSet()) |
| + continue; // There's already a set there. Nothing to do. |
| + } |
| + insertBeforeMulticolChild = placeholder->spanner(); |
| + } else if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) { |
| + // This child is not an immediate predecessor of a spanner, which means that if this |
| + // child precedes a spanner at all, there has to be a column set created for us there |
| + // already. If it doesn't precede any spanner at all, on the other hand, we need a |
| + // column set at the end of the multicol container. We don't really check here if the |
| + // child inserted precedes any spanner or not (as that's an expensive operation). Just |
| + // make sure we have a column set at the end. It's no big deal if it remains unused. |
| + if (!lastSet->nextSibling()) |
| + continue; |
| + } |
| + } |
| + // Need to create a new column set when there's no set already created. We also always insert |
| + // another column set after a spanner. Even if it turns out that there are no renderers |
| + // following the spanner, there may be bottom margins there, which take up space. |
| + RenderMultiColumnSet* newSet = RenderMultiColumnSet::createAnonymous(this, multicolContainer->style()); |
| + multicolContainer->RenderBlock::addChild(newSet, insertBeforeMulticolChild); |
| + invalidateRegions(); |
| + |
| + // We cannot handle immediate column set siblings at the moment (and there's no need for |
| + // it, either). There has to be at least one spanner separating them. |
| + ASSERT(!previousColumnSetOrSpannerSiblingOf(newSet) || !previousColumnSetOrSpannerSiblingOf(newSet)->isRenderMultiColumnSet()); |
| + ASSERT(!nextColumnSetOrSpannerSiblingOf(newSet) || !nextColumnSetOrSpannerSiblingOf(newSet)->isRenderMultiColumnSet()); |
| + } |
| +} |
| + |
| +void RenderMultiColumnFlowThread::flowThreadRelativeWillBeRemoved(RenderObject* relative) |
| +{ |
| + if (m_beingEvacuated) |
| + return; |
| + invalidateRegions(); |
| + if (relative->isRenderMultiColumnSpannerPlaceholder()) { |
| + // Remove the map entry for this spanner, but leave the actual spanner renderer alone. Also |
| + // keep the reference to the spanner, since the placeholder may be about to be re-inserted |
| + // in the tree. |
|
rune
2014/06/19 14:24:32
"into the tree"
mstensho (USE GERRIT)
2014/08/26 09:43:58
Done.
rune
2014/08/27 08:17:38
Acknowledged.
|
| + ASSERT(relative->isDescendantOf(this)); |
| + m_spannerMap.remove(toRenderMultiColumnSpannerPlaceholder(relative)->spanner()); |
| + return; |
| + } |
| + if (relative->style()->columnSpan() == ColumnSpanAll) { |
| + if (relative->parent() != parent()) |
| + return; // not a valid spanner. |
| + |
| + // The placeholder may already have been removed, but if it hasn't, do so now. |
| + if (RenderMultiColumnSpannerPlaceholder* placeholder = m_spannerMap.get(toRenderBox(relative))) { |
| + placeholder->containingBlock()->RenderBlock::removeChild(placeholder); |
| + m_spannerMap.remove(toRenderBox(relative)); |
| + } |
| + |
| + if (RenderObject* next = relative->nextSibling()) { |
| + if (RenderObject* previous = relative->previousSibling()) { |
| + if (previous->isRenderMultiColumnSet() && next->isRenderMultiColumnSet()) { |
| + // Merge two sets that no longer will be separated by a spanner. |
| + next->destroy(); |
| + previous->setNeedsLayoutAndFullRepaint(); |
| + } |
| + } |
| + } |
| + } |
| + // Note that we might end up with empty column sets if all column content is removed. That's no |
| + // big deal though (and locating them would be expensive), and they will be found and re-used if |
| + // content is added again later. |
| +} |
| + |
| +void RenderMultiColumnFlowThread::flowThreadDescendantBoxLaidOut(RenderBox* descendant, LayoutUnit logicalBottomInFlowThread) |
| +{ |
| + if (!descendant->isRenderMultiColumnSpannerPlaceholder()) |
| + return; |
| + RenderMultiColumnSpannerPlaceholder* placeholder = toRenderMultiColumnSpannerPlaceholder(descendant); |
| + |
| + for (RenderBox* prev = previousColumnSetOrSpannerSiblingOf(placeholder->spanner()); prev; prev = previousColumnSetOrSpannerSiblingOf(prev)) { |
| + if (prev->isRenderMultiColumnSet()) { |
| + toRenderMultiColumnSet(prev)->endFlow(logicalBottomInFlowThread); |
| + break; |
| + } |
| + } |
| + |
| + for (RenderBox* next = nextColumnSetOrSpannerSiblingOf(placeholder->spanner()); next; next = nextColumnSetOrSpannerSiblingOf(next)) { |
| + if (next->isRenderMultiColumnSet()) { |
| + m_lastSetWorkedOn = toRenderMultiColumnSet(next); |
| + m_lastSetWorkedOn->beginFlow(logicalBottomInFlowThread); |
| + break; |
| + } |
| + } |
| +} |
| + |
| void RenderMultiColumnFlowThread::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const |
| { |
| // We simply remain at our intrinsic height. |
| @@ -273,9 +518,23 @@ void RenderMultiColumnFlowThread::updateLogicalWidth() |
| void RenderMultiColumnFlowThread::layout() |
| { |
| + ASSERT(!m_inLayout); |
| + m_inLayout = true; |
| + m_lastSetWorkedOn = 0; |
| + if (RenderBox* first = firstColumnSetOrSpanner()) { |
| + if (first->isRenderMultiColumnSet()) { |
| + m_lastSetWorkedOn = toRenderMultiColumnSet(first); |
| + m_lastSetWorkedOn->beginFlow(LayoutUnit()); |
| + } |
| + } |
| RenderFlowThread::layout(); |
| - if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) |
| + if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) { |
| + if (!nextColumnSetOrSpannerSiblingOf(lastSet)) |
| + lastSet->endFlow(logicalHeight()); |
| lastSet->expandToEncompassFlowThreadContentsIfNeeded(); |
| + } |
| + m_inLayout = false; |
| + m_lastSetWorkedOn = 0; |
| } |
| void RenderMultiColumnFlowThread::setPageBreak(LayoutUnit offset, LayoutUnit spaceShortage) |
| @@ -297,16 +556,47 @@ void RenderMultiColumnFlowThread::updateMinimumPageHeight(LayoutUnit offset, Lay |
| multicolSet->updateMinimumColumnHeight(minHeight); |
| } |
| -RenderRegion* RenderMultiColumnFlowThread::regionAtBlockOffset(LayoutUnit /*offset*/) const |
| +RenderRegion* RenderMultiColumnFlowThread::regionAtBlockOffset(LayoutUnit offset) const |
| { |
| - // For now there's only one column set, so this is easy: |
| - return firstMultiColumnSet(); |
|
rune
2014/06/19 14:24:33
A single set is still the case for RenderPagedFlow
mstensho (USE GERRIT)
2014/08/26 09:43:58
Yes.
rune
2014/08/27 08:17:38
Acknowledged.
|
| + if (!m_inLayout) |
| + return RenderFlowThread::regionAtBlockOffset(offset); |
| + |
| + // Layout in progress. We are calculating the set heights as we speak, so the region range |
| + // information is not up-to-date. |
| + |
| + RenderMultiColumnSet* columnSet = m_lastSetWorkedOn ? m_lastSetWorkedOn : firstMultiColumnSet(); |
| + if (!columnSet) { |
| + // If there's no set, bail. This multicol is empty or only consists of spanners. There |
| + // are no regions. |
| + return 0; |
| + } |
| + // The last set worked on is a good guess. But if we're not within the bounds, search for the |
| + // right one. |
| + if (offset < columnSet->logicalTopInFlowThread()) { |
| + do { |
| + if (RenderMultiColumnSet* prev = columnSet->previousSiblingMultiColumnSet()) |
| + columnSet = prev; |
| + else |
| + break; |
| + } while (offset < columnSet->logicalTopInFlowThread()); |
| + } else { |
| + while (offset >= columnSet->logicalBottomInFlowThread()) { |
| + RenderMultiColumnSet* next = columnSet->nextSiblingMultiColumnSet(); |
| + if (!next || !next->hasBeenFlowed()) |
| + break; |
|
rune
2014/06/19 14:24:33
If I understand this correctly, if we ask for a re
mstensho (USE GERRIT)
2014/08/26 09:43:58
Done.
I'm not aware of any usecase for searching
rune
2014/08/27 08:17:38
Acknowledged.
|
| + columnSet = next; |
| + } |
| + } |
| + return columnSet; |
| } |
| -bool RenderMultiColumnFlowThread::addForcedRegionBreak(LayoutUnit offset, RenderObject* /*breakChild*/, bool /*isBefore*/, LayoutUnit* offsetBreakAdjustment) |
| +bool RenderMultiColumnFlowThread::addForcedRegionBreak(LayoutUnit offset, RenderObject* breakChild, bool /*isBefore*/, LayoutUnit* offsetBreakAdjustment) |
| { |
| if (RenderMultiColumnSet* multicolSet = toRenderMultiColumnSet(regionAtBlockOffset(offset))) { |
| - multicolSet->addContentRun(offset); |
| + // Spanner placeholders force a break (to make sure that the unused part of the last column |
| + // is empty), but this break should not affect column balancing. |
| + if (!breakChild->isRenderMultiColumnSpannerPlaceholder()) |
| + multicolSet->addContentRun(offset); |
| if (offsetBreakAdjustment) |
| *offsetBreakAdjustment = pageLogicalHeightForOffset(offset) ? pageRemainingLogicalHeightForOffset(offset, IncludePageBoundary) : LayoutUnit(); |
| return true; |