LayoutMultiColumnSet* LayoutMultiColumnFlowThread::mapDescendantToColumnSet(LayoutObject* layoutObject) const { ASSERT(!containingColumnSpannerPlaceholder(layoutObject)); // should not be used for spanners or content inside them. ASSERT(layoutObject != this); ASSERT(layoutObject->isDescendantOf(this)); ASSERT(layoutObject->containingBlock()->isDescendantOf(this)); // Out-of-flow objects don't belong in column sets. ASSERT(layoutObject->flowThreadContainingBlock() == this); ASSERT(!layoutObject->isLayoutMultiColumnSet()); ASSERT(!layoutObject->isLayoutMultiColumnSpannerPlaceholder()); LayoutMultiColumnSet* multicolSet = firstMultiColumnSet(); if (!multicolSet) return nullptr; if (!multicolSet->nextSiblingMultiColumnSet()) return multicolSet; // This is potentially SLOW! But luckily very uncommon. You would have to dynamically insert a // spanner into the middle of column contents to need this. for (; multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) { LayoutObject* firstLayoutObject = firstLayoutObjectInSet(multicolSet); LayoutObject* lastLayoutObject = lastLayoutObjectInSet(multicolSet); ASSERT(firstLayoutObject); for (LayoutObject* walker = firstLayoutObject; walker; walker = walker->nextInPreOrder(this)) { if (walker == layoutObject) return multicolSet; if (walker == lastLayoutObject) break; } } return nullptr; }
RenderRegion* RenderMultiColumnFlowThread::regionAtBlockOffset(const RenderBox* box, LayoutUnit offset, bool extendLastRegion, RegionAutoGenerationPolicy autoGenerationPolicy) { if (!m_inLayout) return RenderFlowThread::regionAtBlockOffset(box, offset, extendLastRegion, autoGenerationPolicy); // 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 nullptr; } // 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; columnSet = next; } } return columnSet; }
void RenderMultiColumnFlowThread::evacuateAndDestroy() { RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); m_beingEvacuated = true; // Delete the line box tree. deleteLines(); LayoutStateDisabler layoutStateDisabler(&view()); // 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->setMultiColumnFlowThread(nullptr); 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(); destroy(); }
RenderMultiColumnSet* RenderMultiColumnFlowThread::findSetRendering(RenderObject* renderer) const { for (RenderMultiColumnSet* multicolSet = firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) { if (multicolSet->containsRendererInFlowThread(renderer)) return multicolSet; } return nullptr; }
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(); }
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; }
bool LayoutMultiColumnFlowThread::recalculateColumnHeights() { // All column sets that needed layout have now been laid out, so we can finally validate them. validateColumnSets(); if (!m_needsColumnHeightsRecalculation) return false; // Column heights may change here because of balancing. We may have to do multiple layout // passes, depending on how the contents is fitted to the changed column heights. In most // cases, laying out again twice or even just once will suffice. Sometimes we need more // passes than that, though, but the number of retries should not exceed the number of // columns, unless we have a bug. bool needsRelayout = false; for (LayoutMultiColumnSet* multicolSet = firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) needsRelayout |= multicolSet->recalculateColumnHeight(); if (needsRelayout) setChildNeedsLayout(MarkOnlyThis); m_inBalancingPass = needsRelayout; return needsRelayout; }
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::flowThreadDescendantWillBeRemoved(LayoutObject* descendant) { // This method ensures that the list of column sets and spanner placeholders reflects the // multicol content that we'll be left with after removal of a descendant (or descendant // subtree). See the header file for more information. Removing content may mean that we need to // remove column sets and/or spanner placeholders. if (m_isBeingEvacuated) return; if (shouldSkipInsertedOrRemovedChild(this, *descendant)) return; bool hadContainingPlaceholder = containingColumnSpannerPlaceholder(descendant); bool processedSomething = false; LayoutObject* next; // Remove spanner placeholders that are no longer needed, and merge column sets around them. for (LayoutObject* layoutObject = descendant; layoutObject; layoutObject = next) { if (layoutObject != descendant && shouldSkipInsertedOrRemovedChild(this, *layoutObject)) { next = layoutObject->nextInPreOrderAfterChildren(descendant); continue; } processedSomething = true; LayoutMultiColumnSpannerPlaceholder* placeholder = layoutObject->spannerPlaceholder(); if (!placeholder) { next = layoutObject->nextInPreOrder(descendant); continue; } next = layoutObject->nextInPreOrderAfterChildren(descendant); // It's a spanner. Its children are of no interest to us. destroySpannerPlaceholder(placeholder); } if (hadContainingPlaceholder || !processedSomething) return; // No column content will be removed, so we can stop here. // Column content will be removed. Does this mean that we should destroy a column set? LayoutMultiColumnSpannerPlaceholder* adjacentPreviousSpannerPlaceholder = nullptr; LayoutObject* previousLayoutObject = previousInPreOrderSkippingOutOfFlow(this, descendant); if (previousLayoutObject && previousLayoutObject != this) { adjacentPreviousSpannerPlaceholder = containingColumnSpannerPlaceholder(previousLayoutObject); if (!adjacentPreviousSpannerPlaceholder) return; // Preceded by column content. Set still needed. } LayoutMultiColumnSpannerPlaceholder* adjacentNextSpannerPlaceholder = nullptr; LayoutObject* nextLayoutObject = nextInPreOrderAfterChildrenSkippingOutOfFlow(this, descendant); if (nextLayoutObject) { adjacentNextSpannerPlaceholder = containingColumnSpannerPlaceholder(nextLayoutObject); if (!adjacentNextSpannerPlaceholder) return; // Followed by column content. Set still needed. } // We have now determined that, with the removal of |descendant|, we should remove a column // set. Locate it and remove it. Do it without involving mapDescendantToColumnSet(), as that // might be very slow. Deduce the right set from the spanner placeholders that we've already // found. LayoutMultiColumnSet* columnSetToRemove; if (adjacentNextSpannerPlaceholder) { columnSetToRemove = toLayoutMultiColumnSet(adjacentNextSpannerPlaceholder->previousSiblingMultiColumnBox()); ASSERT(!adjacentPreviousSpannerPlaceholder || columnSetToRemove == adjacentPreviousSpannerPlaceholder->nextSiblingMultiColumnBox()); } else if (adjacentPreviousSpannerPlaceholder) { columnSetToRemove = toLayoutMultiColumnSet(adjacentPreviousSpannerPlaceholder->nextSiblingMultiColumnBox()); } else { // If there were no adjacent spanners, it has to mean that there's only one column set, // since it's only spanners that may cause creation of multiple sets. columnSetToRemove = firstMultiColumnSet(); ASSERT(columnSetToRemove); ASSERT(!columnSetToRemove->nextSiblingMultiColumnSet()); } ASSERT(columnSetToRemove); columnSetToRemove->destroy(); }
void LayoutMultiColumnFlowThread::columnRuleStyleDidChange() { for (LayoutMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) columnSet->setShouldDoFullPaintInvalidation(PaintInvalidationStyleChange); }