2 * Copyright (C) 2012 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS IN..0TERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "core/layout/LayoutMultiColumnFlowThread.h"
29 #include "core/layout/LayoutMultiColumnSet.h"
30 #include "core/layout/LayoutMultiColumnSpannerPlaceholder.h"
31 #include "core/layout/MultiColumnFragmentainerGroup.h"
35 LayoutMultiColumnFlowThread::LayoutMultiColumnFlowThread()
36 : m_lastSetWorkedOn(nullptr)
38 , m_columnHeightAvailable(0)
39 , m_inBalancingPass(false)
40 , m_needsColumnHeightsRecalculation(false)
41 , m_progressionIsInline(true)
42 , m_isBeingEvacuated(false)
44 setIsInsideFlowThread(true);
47 LayoutMultiColumnFlowThread::~LayoutMultiColumnFlowThread()
51 LayoutMultiColumnFlowThread
* LayoutMultiColumnFlowThread::createAnonymous(Document
& document
, const ComputedStyle
& parentStyle
)
53 LayoutMultiColumnFlowThread
* layoutObject
= new LayoutMultiColumnFlowThread();
54 layoutObject
->setDocumentForAnonymous(&document
);
55 layoutObject
->setStyle(ComputedStyle::createAnonymousStyleWithDisplay(parentStyle
, BLOCK
));
59 LayoutMultiColumnSet
* LayoutMultiColumnFlowThread::firstMultiColumnSet() const
61 for (LayoutObject
* sibling
= nextSibling(); sibling
; sibling
= sibling
->nextSibling()) {
62 if (sibling
->isLayoutMultiColumnSet())
63 return toLayoutMultiColumnSet(sibling
);
68 LayoutMultiColumnSet
* LayoutMultiColumnFlowThread::lastMultiColumnSet() const
70 for (LayoutObject
* sibling
= multiColumnBlockFlow()->lastChild(); sibling
; sibling
= sibling
->previousSibling()) {
71 if (sibling
->isLayoutMultiColumnSet())
72 return toLayoutMultiColumnSet(sibling
);
77 static inline bool isMultiColumnContainer(const LayoutObject
& object
)
79 if (!object
.isLayoutBlockFlow())
81 return toLayoutBlockFlow(object
).multiColumnFlowThread();
84 // Find the next layout object that has the multicol container in its containing block chain, skipping nested multicol containers.
85 static LayoutObject
* nextInPreOrderAfterChildrenSkippingOutOfFlow(LayoutMultiColumnFlowThread
* flowThread
, LayoutObject
* descendant
)
87 ASSERT(descendant
->isDescendantOf(flowThread
));
88 LayoutObject
* object
= descendant
->nextInPreOrderAfterChildren(flowThread
);
90 // Walk through the siblings and find the first one which is either in-flow or has this
91 // flow thread as its containing block flow thread.
92 if (!object
->isOutOfFlowPositioned())
94 if (object
->containingBlock()->flowThreadContainingBlock() == flowThread
) {
95 // This out-of-flow object is still part of the flow thread, because its containing
96 // block (probably relatively positioned) is part of the flow thread.
99 object
= object
->nextInPreOrderAfterChildren(flowThread
);
104 // Make sure that we didn't stumble into an inner multicol container.
105 for (LayoutObject
* walker
= object
->parent(); walker
&& walker
!= flowThread
; walker
= walker
->parent())
106 ASSERT(!isMultiColumnContainer(*walker
));
111 // Find the previous layout object that has the multicol container in its containing block chain, skipping nested multicol containers.
112 static LayoutObject
* previousInPreOrderSkippingOutOfFlow(LayoutMultiColumnFlowThread
* flowThread
, LayoutObject
* descendant
)
114 ASSERT(descendant
->isDescendantOf(flowThread
));
115 LayoutObject
* object
= descendant
->previousInPreOrder(flowThread
);
116 while (object
&& object
!= flowThread
) {
117 if (object
->isColumnSpanAll()) {
118 LayoutMultiColumnFlowThread
* placeholderFlowThread
= toLayoutBox(object
)->spannerPlaceholder()->flowThread();
119 if (placeholderFlowThread
== flowThread
)
121 // We're inside an inner multicol container. We have no business there. Continue on the outside.
122 object
= placeholderFlowThread
->parent();
123 ASSERT(object
->isDescendantOf(flowThread
));
126 if (object
->flowThreadContainingBlock() == flowThread
) {
127 LayoutObject
* ancestor
;
128 for (ancestor
= object
->parent(); ; ancestor
= ancestor
->parent()) {
129 if (ancestor
== flowThread
)
131 if (isMultiColumnContainer(*ancestor
)) {
132 // We're inside an inner multicol container. We have no business there.
137 ASSERT(ancestor
->isDescendantOf(flowThread
));
138 continue; // Continue on the outside of the inner flow thread.
140 // We're inside something that's out-of-flow. Keep looking upwards and backwards in the tree.
141 object
= object
->previousInPreOrder(flowThread
);
143 if (!object
|| object
== flowThread
)
146 // Make sure that we didn't stumble into an inner multicol container.
147 for (LayoutObject
* walker
= object
->parent(); walker
&& walker
!= flowThread
; walker
= walker
->parent())
148 ASSERT(!isMultiColumnContainer(*walker
));
153 static LayoutObject
* firstLayoutObjectInSet(LayoutMultiColumnSet
* multicolSet
)
155 LayoutBox
* sibling
= multicolSet
->previousSiblingMultiColumnBox();
157 return multicolSet
->flowThread()->firstChild();
158 // Adjacent column content sets should not occur. We would have no way of figuring out what each
159 // of them contains then.
160 ASSERT(sibling
->isLayoutMultiColumnSpannerPlaceholder());
161 LayoutBox
* spanner
= toLayoutMultiColumnSpannerPlaceholder(sibling
)->layoutObjectInFlowThread();
162 return nextInPreOrderAfterChildrenSkippingOutOfFlow(multicolSet
->multiColumnFlowThread(), spanner
);
165 static LayoutObject
* lastLayoutObjectInSet(LayoutMultiColumnSet
* multicolSet
)
167 LayoutBox
* sibling
= multicolSet
->nextSiblingMultiColumnBox();
169 return nullptr; // By right we should return lastLeafChild() here, but the caller doesn't care, so just return 0.
170 // Adjacent column content sets should not occur. We would have no way of figuring out what each
171 // of them contains then.
172 ASSERT(sibling
->isLayoutMultiColumnSpannerPlaceholder());
173 LayoutBox
* spanner
= toLayoutMultiColumnSpannerPlaceholder(sibling
)->layoutObjectInFlowThread();
174 return previousInPreOrderSkippingOutOfFlow(multicolSet
->multiColumnFlowThread(), spanner
);
177 LayoutMultiColumnSet
* LayoutMultiColumnFlowThread::mapDescendantToColumnSet(LayoutObject
* layoutObject
) const
179 ASSERT(!containingColumnSpannerPlaceholder(layoutObject
)); // should not be used for spanners or content inside them.
180 ASSERT(layoutObject
!= this);
181 ASSERT(layoutObject
->isDescendantOf(this));
182 ASSERT(layoutObject
->containingBlock()->isDescendantOf(this)); // Out-of-flow objects don't belong in column sets.
183 ASSERT(layoutObject
->flowThreadContainingBlock() == this);
184 ASSERT(!layoutObject
->isLayoutMultiColumnSet());
185 ASSERT(!layoutObject
->isLayoutMultiColumnSpannerPlaceholder());
186 LayoutMultiColumnSet
* multicolSet
= firstMultiColumnSet();
189 if (!multicolSet
->nextSiblingMultiColumnSet())
192 // This is potentially SLOW! But luckily very uncommon. You would have to dynamically insert a
193 // spanner into the middle of column contents to need this.
194 for (; multicolSet
; multicolSet
= multicolSet
->nextSiblingMultiColumnSet()) {
195 LayoutObject
* firstLayoutObject
= firstLayoutObjectInSet(multicolSet
);
196 LayoutObject
* lastLayoutObject
= lastLayoutObjectInSet(multicolSet
);
197 ASSERT(firstLayoutObject
);
199 for (LayoutObject
* walker
= firstLayoutObject
; walker
; walker
= walker
->nextInPreOrder(this)) {
200 if (walker
== layoutObject
)
202 if (walker
== lastLayoutObject
)
210 LayoutMultiColumnSpannerPlaceholder
* LayoutMultiColumnFlowThread::containingColumnSpannerPlaceholder(const LayoutObject
* descendant
) const
212 ASSERT(descendant
->isDescendantOf(this));
214 // Before we spend time on searching the ancestry, see if there's a quick way to determine
215 // whether there might be any spanners at all.
216 LayoutBox
* firstBox
= firstMultiColumnBox();
217 if (!firstBox
|| (firstBox
== lastMultiColumnBox() && firstBox
->isLayoutMultiColumnSet()))
220 // We have spanners. See if the layoutObject in question is one or inside of one then.
221 for (const LayoutObject
* ancestor
= descendant
; ancestor
&& ancestor
!= this; ancestor
= ancestor
->parent()) {
222 if (LayoutMultiColumnSpannerPlaceholder
* placeholder
= ancestor
->spannerPlaceholder())
228 void LayoutMultiColumnFlowThread::populate()
230 LayoutBlockFlow
* multicolContainer
= multiColumnBlockFlow();
231 ASSERT(!nextSibling());
232 // Reparent children preceding the flow thread into the flow thread. It's multicol content
233 // now. At this point there's obviously nothing after the flow thread, but layoutObjects (column
234 // sets and spanners) will be inserted there as we insert elements into the flow thread.
235 multicolContainer
->moveChildrenTo(this, multicolContainer
->firstChild(), this, true);
238 void LayoutMultiColumnFlowThread::evacuateAndDestroy()
240 LayoutBlockFlow
* multicolContainer
= multiColumnBlockFlow();
241 m_isBeingEvacuated
= true;
243 // Remove all sets and spanners.
244 while (LayoutBox
* columnBox
= firstMultiColumnBox()) {
245 ASSERT(columnBox
->isAnonymous());
246 columnBox
->destroy();
249 ASSERT(!previousSibling());
250 ASSERT(!nextSibling());
252 // Finally we can promote all flow thread's children. Before we move them to the flow thread's
253 // container, we need to unregister the flow thread, so that they aren't just re-added again to
254 // the flow thread that we're trying to empty.
255 multicolContainer
->resetMultiColumnFlowThread();
256 moveAllChildrenTo(multicolContainer
, true);
258 // We used to manually nuke the line box tree here, but that should happen automatically when
259 // moving children around (the code above).
260 ASSERT(!firstLineBox());
265 LayoutSize
LayoutMultiColumnFlowThread::columnOffset(const LayoutPoint
& point
) const
267 if (!hasValidColumnSetInfo())
268 return LayoutSize(0, 0);
270 LayoutPoint flowThreadPoint
= flipForWritingMode(point
);
271 LayoutUnit blockOffset
= isHorizontalWritingMode() ? flowThreadPoint
.y() : flowThreadPoint
.x();
272 return flowThreadTranslationAtOffset(blockOffset
);
275 bool LayoutMultiColumnFlowThread::needsNewWidth() const
278 unsigned dummyColumnCount
; // We only care if used column-width changes.
279 calculateColumnCountAndWidth(newWidth
, dummyColumnCount
);
280 return newWidth
!= logicalWidth();
283 bool LayoutMultiColumnFlowThread::isPageLogicalHeightKnown() const
285 if (LayoutMultiColumnSet
* columnSet
= lastMultiColumnSet())
286 return columnSet
->isPageLogicalHeightKnown();
290 LayoutSize
LayoutMultiColumnFlowThread::flowThreadTranslationAtOffset(LayoutUnit offsetInFlowThread
) const
292 LayoutMultiColumnSet
* columnSet
= columnSetAtBlockOffset(offsetInFlowThread
);
294 return LayoutSize(0, 0);
295 return columnSet
->flowThreadTranslationAtOffset(offsetInFlowThread
);
298 LayoutPoint
LayoutMultiColumnFlowThread::visualPointToFlowThreadPoint(const LayoutPoint
& visualPoint
) const
300 LayoutUnit blockOffset
= isHorizontalWritingMode() ? visualPoint
.y() : visualPoint
.x();
301 const LayoutMultiColumnSet
* columnSet
= nullptr;
302 for (const LayoutMultiColumnSet
* candidate
= firstMultiColumnSet(); candidate
; candidate
= candidate
->nextSiblingMultiColumnSet()) {
303 columnSet
= candidate
;
304 if (candidate
->logicalBottom() > blockOffset
)
307 return columnSet
? columnSet
->visualPointToFlowThreadPoint(toLayoutPoint(visualPoint
+ location() - columnSet
->location())) : visualPoint
;
310 LayoutMultiColumnSet
* LayoutMultiColumnFlowThread::columnSetAtBlockOffset(LayoutUnit offset
) const
312 if (LayoutMultiColumnSet
* columnSet
= m_lastSetWorkedOn
) {
313 // Layout in progress. We are calculating the set heights as we speak, so the column set range
314 // information is not up-to-date.
315 while (columnSet
->logicalTopInFlowThread() > offset
) {
316 // Sometimes we have to use a previous set. This happens when we're working with a block
317 // that contains a spanner (so that there's a column set both before and after the
318 // spanner, and both sets contain said block).
319 LayoutMultiColumnSet
* previousSet
= columnSet
->previousSiblingMultiColumnSet();
322 columnSet
= previousSet
;
327 ASSERT(!m_columnSetsInvalidated
);
328 if (m_multiColumnSetList
.isEmpty())
331 return m_multiColumnSetList
.first();
333 MultiColumnSetSearchAdapter
adapter(offset
);
334 m_multiColumnSetIntervalTree
.allOverlapsWithAdapter
<MultiColumnSetSearchAdapter
>(adapter
);
336 // If no set was found, the offset is in the flow thread overflow.
337 if (!adapter
.result() && !m_multiColumnSetList
.isEmpty())
338 return m_multiColumnSetList
.last();
339 return adapter
.result();
342 void LayoutMultiColumnFlowThread::layoutColumns(bool relayoutChildren
, SubtreeLayoutScope
& layoutScope
)
344 if (relayoutChildren
)
345 layoutScope
.setChildNeedsLayout(this);
347 m_needsColumnHeightsRecalculation
= false;
348 if (!needsLayout()) {
349 // Just before the multicol container (our parent LayoutBlockFlow) finishes laying out, it
350 // will call recalculateColumnHeights() on us unconditionally, but we only want that method
351 // to do any work if we actually laid out the flow thread. Otherwise, the balancing
352 // machinery would kick in needlessly, and trigger additional layout passes. Furthermore, we
353 // actually depend on a proper flowthread layout pass in order to do balancing, since it's
354 // flowthread layout that sets up content runs.
358 m_blockOffsetInEnclosingFlowThread
= enclosingFlowThread() ? multiColumnBlockFlow()->offsetFromLogicalTopOfFirstPage() : LayoutUnit();
360 for (LayoutBox
* columnBox
= firstMultiColumnBox(); columnBox
; columnBox
= columnBox
->nextSiblingMultiColumnBox()) {
361 if (!columnBox
->isLayoutMultiColumnSet()) {
362 ASSERT(columnBox
->isLayoutMultiColumnSpannerPlaceholder()); // no other type is expected.
363 m_needsColumnHeightsRecalculation
= true;
366 LayoutMultiColumnSet
* columnSet
= toLayoutMultiColumnSet(columnBox
);
367 if (!m_inBalancingPass
) {
368 // This is the initial layout pass. We need to reset the column height, because contents
369 // typically have changed.
370 columnSet
->resetColumnHeight();
372 if (!m_needsColumnHeightsRecalculation
)
373 m_needsColumnHeightsRecalculation
= columnSet
->heightIsAuto();
376 invalidateColumnSets();
380 bool LayoutMultiColumnFlowThread::recalculateColumnHeights()
382 // All column sets that needed layout have now been laid out, so we can finally validate them.
383 validateColumnSets();
385 if (!m_needsColumnHeightsRecalculation
)
388 // Column heights may change here because of balancing. We may have to do multiple layout
389 // passes, depending on how the contents is fitted to the changed column heights. In most
390 // cases, laying out again twice or even just once will suffice. Sometimes we need more
391 // passes than that, though, but the number of retries should not exceed the number of
392 // columns, unless we have a bug.
393 bool needsRelayout
= false;
394 for (LayoutMultiColumnSet
* multicolSet
= firstMultiColumnSet(); multicolSet
; multicolSet
= multicolSet
->nextSiblingMultiColumnSet()) {
395 needsRelayout
|= multicolSet
->recalculateColumnHeight(m_inBalancingPass
? StretchBySpaceShortage
: GuessFromFlowThreadPortion
);
397 // Once a column set gets a new column height, that column set and all successive column
398 // sets need to be laid out over again, since their logical top will be affected by
399 // this, and therefore their column heights may change as well, at least if the multicol
400 // height is constrained.
401 multicolSet
->setChildNeedsLayout(MarkOnlyThis
);
406 setChildNeedsLayout(MarkOnlyThis
);
408 m_inBalancingPass
= needsRelayout
;
409 return needsRelayout
;
412 void LayoutMultiColumnFlowThread::columnRuleStyleDidChange()
414 for (LayoutMultiColumnSet
* columnSet
= firstMultiColumnSet(); columnSet
; columnSet
= columnSet
->nextSiblingMultiColumnSet())
415 columnSet
->setShouldDoFullPaintInvalidation(PaintInvalidationStyleChange
);
418 bool LayoutMultiColumnFlowThread::removeSpannerPlaceholderIfNoLongerValid(LayoutBox
* spannerObjectInFlowThread
)
420 ASSERT(spannerObjectInFlowThread
->spannerPlaceholder());
421 if (descendantIsValidColumnSpanner(spannerObjectInFlowThread
))
422 return false; // Still a valid spanner.
424 // No longer a valid spanner. Get rid of the placeholder.
425 destroySpannerPlaceholder(spannerObjectInFlowThread
->spannerPlaceholder());
426 ASSERT(!spannerObjectInFlowThread
->spannerPlaceholder());
428 // We may have a new containing block, since we're no longer a spanner. Mark it for relayout.
429 spannerObjectInFlowThread
->containingBlock()->setNeedsLayoutAndPrefWidthsRecalc(LayoutInvalidationReason::ColumnsChanged
);
431 // Now generate a column set for this ex-spanner, if needed and none is there for us already.
432 flowThreadDescendantWasInserted(spannerObjectInFlowThread
);
437 LayoutMultiColumnFlowThread
* LayoutMultiColumnFlowThread::enclosingFlowThread() const
439 if (multiColumnBlockFlow()->isInsideFlowThread())
440 return toLayoutMultiColumnFlowThread(locateFlowThreadContainingBlockOf(*multiColumnBlockFlow()));
444 bool LayoutMultiColumnFlowThread::hasFragmentainerGroupForColumnAt(LayoutUnit offsetInFlowThread
) const
446 // If there's no enclosing flow thread, there'll always be only one fragmentainer group, and it
447 // can hold as many columns as we like. We shouldn't even be here in that case.
448 ASSERT(enclosingFlowThread());
450 LayoutMultiColumnSet
* lastColumnSet
= lastMultiColumnSet();
451 if (!lastColumnSet
) {
452 ASSERT_NOT_REACHED();
455 if (lastColumnSet
->logicalTopInFlowThread() > offsetInFlowThread
)
457 const MultiColumnFragmentainerGroup
& lastRow
= lastColumnSet
->lastFragmentainerGroup();
458 if (lastRow
.logicalTopInFlowThread() > offsetInFlowThread
)
460 return offsetInFlowThread
- lastRow
.logicalTopInFlowThread() < lastRow
.logicalHeight() * lastColumnSet
->usedColumnCount();
463 void LayoutMultiColumnFlowThread::appendNewFragmentainerGroupIfNeeded(LayoutUnit offsetInFlowThread
)
465 LayoutMultiColumnFlowThread
* enclosingFlowThread
= this->enclosingFlowThread();
466 if (!enclosingFlowThread
)
467 return; // Not nested. We'll never need more rows than the one we already have then.
468 if (!hasFragmentainerGroupForColumnAt(offsetInFlowThread
)) {
469 // We have run out of columns here, so we add another row to hold more columns. When we add
470 // a new row, it implicitly means that we're inserting another column in our enclosing
471 // multicol container. That in turn may mean that we've run out of columns there too.
472 const MultiColumnFragmentainerGroup
& newRow
= lastMultiColumnSet()->appendNewFragmentainerGroup();
473 enclosingFlowThread
->appendNewFragmentainerGroupIfNeeded(newRow
.blockOffsetInEnclosingFlowThread());
477 void LayoutMultiColumnFlowThread::calculateColumnCountAndWidth(LayoutUnit
& width
, unsigned& count
) const
479 LayoutBlock
* columnBlock
= multiColumnBlockFlow();
480 const ComputedStyle
* columnStyle
= columnBlock
->style();
481 LayoutUnit availableWidth
= columnBlock
->contentLogicalWidth();
482 LayoutUnit columnGap
= columnBlock
->columnGap();
483 LayoutUnit computedColumnWidth
= max
<LayoutUnit
>(1, LayoutUnit(columnStyle
->columnWidth()));
484 unsigned computedColumnCount
= max
<int>(1, columnStyle
->columnCount());
486 ASSERT(!columnStyle
->hasAutoColumnCount() || !columnStyle
->hasAutoColumnWidth());
487 if (columnStyle
->hasAutoColumnWidth() && !columnStyle
->hasAutoColumnCount()) {
488 count
= computedColumnCount
;
489 width
= std::max
<LayoutUnit
>(0, (availableWidth
- ((count
- 1) * columnGap
)) / count
);
490 } else if (!columnStyle
->hasAutoColumnWidth() && columnStyle
->hasAutoColumnCount()) {
491 count
= std::max
<LayoutUnit
>(1, (availableWidth
+ columnGap
) / (computedColumnWidth
+ columnGap
));
492 width
= ((availableWidth
+ columnGap
) / count
) - columnGap
;
494 count
= std::max
<LayoutUnit
>(std::min
<LayoutUnit
>(computedColumnCount
, (availableWidth
+ columnGap
) / (computedColumnWidth
+ columnGap
)), 1);
495 width
= ((availableWidth
+ columnGap
) / count
) - columnGap
;
499 void LayoutMultiColumnFlowThread::createAndInsertMultiColumnSet(LayoutBox
* insertBefore
)
501 LayoutBlockFlow
* multicolContainer
= multiColumnBlockFlow();
502 LayoutMultiColumnSet
* newSet
= LayoutMultiColumnSet::createAnonymous(*this, multicolContainer
->styleRef());
503 multicolContainer
->LayoutBlock::addChild(newSet
, insertBefore
);
504 invalidateColumnSets();
506 // We cannot handle immediate column set siblings (and there's no need for it, either).
507 // There has to be at least one spanner separating them.
508 ASSERT(!newSet
->previousSiblingMultiColumnBox() || !newSet
->previousSiblingMultiColumnBox()->isLayoutMultiColumnSet());
509 ASSERT(!newSet
->nextSiblingMultiColumnBox() || !newSet
->nextSiblingMultiColumnBox()->isLayoutMultiColumnSet());
512 void LayoutMultiColumnFlowThread::createAndInsertSpannerPlaceholder(LayoutBox
* spannerObjectInFlowThread
, LayoutObject
* insertedBeforeInFlowThread
)
514 LayoutBox
* insertBeforeColumnBox
= nullptr;
515 LayoutMultiColumnSet
* setToSplit
= nullptr;
516 if (insertedBeforeInFlowThread
) {
517 // The spanner is inserted before something. Figure out what this entails. If the
518 // next object is a spanner too, it means that we can simply insert a new spanner
519 // placeholder in front of its placeholder.
520 insertBeforeColumnBox
= insertedBeforeInFlowThread
->spannerPlaceholder();
521 if (!insertBeforeColumnBox
) {
522 // The next object isn't a spanner; it's regular column content. Examine what
523 // comes right before us in the flow thread, then.
524 LayoutObject
* previousLayoutObject
= previousInPreOrderSkippingOutOfFlow(this, spannerObjectInFlowThread
);
525 if (!previousLayoutObject
|| previousLayoutObject
== this) {
526 // The spanner is inserted as the first child of the multicol container,
527 // which means that we simply insert a new spanner placeholder at the
529 insertBeforeColumnBox
= firstMultiColumnBox();
530 } else if (LayoutMultiColumnSpannerPlaceholder
* previousPlaceholder
= containingColumnSpannerPlaceholder(previousLayoutObject
)) {
531 // Before us is another spanner. We belong right after it then.
532 insertBeforeColumnBox
= previousPlaceholder
->nextSiblingMultiColumnBox();
534 // We're inside regular column content with both feet. Find out which column
535 // set this is. It needs to be split it into two sets, so that we can insert
536 // a new spanner placeholder between them.
537 setToSplit
= mapDescendantToColumnSet(previousLayoutObject
);
538 ASSERT(setToSplit
== mapDescendantToColumnSet(insertedBeforeInFlowThread
));
539 setToSplit
->setNeedsLayoutAndFullPaintInvalidation(LayoutInvalidationReason::ColumnsChanged
);
540 insertBeforeColumnBox
= setToSplit
->nextSiblingMultiColumnBox();
541 // We've found out which set that needs to be split. Now proceed to
542 // inserting the spanner placeholder, and then insert a second column set.
545 ASSERT(setToSplit
|| insertBeforeColumnBox
);
548 LayoutBlockFlow
* multicolContainer
= multiColumnBlockFlow();
549 LayoutMultiColumnSpannerPlaceholder
* newPlaceholder
= LayoutMultiColumnSpannerPlaceholder::createAnonymous(multicolContainer
->styleRef(), *spannerObjectInFlowThread
);
550 ASSERT(!insertBeforeColumnBox
|| insertBeforeColumnBox
->parent() == multicolContainer
);
551 multicolContainer
->LayoutBlock::addChild(newPlaceholder
, insertBeforeColumnBox
);
552 spannerObjectInFlowThread
->setSpannerPlaceholder(*newPlaceholder
);
555 createAndInsertMultiColumnSet(insertBeforeColumnBox
);
558 void LayoutMultiColumnFlowThread::destroySpannerPlaceholder(LayoutMultiColumnSpannerPlaceholder
* placeholder
)
560 if (LayoutBox
* nextColumnBox
= placeholder
->nextSiblingMultiColumnBox()) {
561 LayoutBox
* previousColumnBox
= placeholder
->previousSiblingMultiColumnBox();
562 if (nextColumnBox
&& nextColumnBox
->isLayoutMultiColumnSet()
563 && previousColumnBox
&& previousColumnBox
->isLayoutMultiColumnSet()) {
564 // Need to merge two column sets.
565 nextColumnBox
->destroy();
566 previousColumnBox
->setNeedsLayout(LayoutInvalidationReason::ColumnsChanged
);
567 invalidateColumnSets();
570 placeholder
->destroy();
573 bool LayoutMultiColumnFlowThread::descendantIsValidColumnSpanner(LayoutObject
* descendant
) const
575 // This method needs to behave correctly in the following situations:
576 // - When the descendant doesn't have a spanner placeholder but should have one (return true)
577 // - When the descendant doesn't have a spanner placeholder and still should not have one (return false)
578 // - When the descendant has a spanner placeholder but should no longer have one (return false)
579 // - When the descendant has a spanner placeholder and should still have one (return true)
581 // We assume that we're inside the flow thread. This function is not to be called otherwise.
582 ASSERT(descendant
->isDescendantOf(this));
584 // The spec says that column-span only applies to in-flow block-level elements.
585 if (descendant
->style()->columnSpan() != ColumnSpanAll
|| !descendant
->isBox() || descendant
->isInline() || descendant
->isFloatingOrOutOfFlowPositioned())
588 if (!descendant
->containingBlock()->isLayoutBlockFlow()) {
589 // Needs to be in a block-flow container, and not e.g. a table.
593 // This looks like a spanner, but if we're inside something unbreakable, it's not to be treated as one.
594 for (LayoutBox
* ancestor
= toLayoutBox(descendant
)->parentBox(); ancestor
; ancestor
= ancestor
->containingBlock()) {
595 if (ancestor
->isLayoutFlowThread()) {
596 ASSERT(ancestor
== this);
599 if (ancestor
->spannerPlaceholder()) {
600 // FIXME: do we want to support nested spanners in a different way? The outer spanner
601 // has already broken out from the columns to become sized by the multicol container,
602 // which may be good enough for the inner spanner. But margins, borders, padding and
603 // explicit widths on the outer spanner, or on any children between the outer and inner
604 // spanner, will affect the width of the inner spanner this way, which might be
605 // undesirable. The spec has nothing to say on the matter.
606 return false; // Ignore nested spanners.
608 if (ancestor
->isFloatingOrOutOfFlowPositioned()) {
609 // TODO(mstensho): It could actually be nice to support this (although the usefulness is
610 // probably very limited), but currently our column balancing algorithm gets confused
611 // when a spanner is inside a float, because a float's position isn't always known until
612 // after layout. Similarly for absolutely positioned boxes.
615 if (ancestor
->isUnsplittableForPagination())
618 ASSERT_NOT_REACHED();
622 void LayoutMultiColumnFlowThread::addColumnSetToThread(LayoutMultiColumnSet
* columnSet
)
624 if (LayoutMultiColumnSet
* nextSet
= columnSet
->nextSiblingMultiColumnSet()) {
625 LayoutMultiColumnSetList::iterator it
= m_multiColumnSetList
.find(nextSet
);
626 ASSERT(it
!= m_multiColumnSetList
.end());
627 m_multiColumnSetList
.insertBefore(it
, columnSet
);
629 m_multiColumnSetList
.add(columnSet
);
633 void LayoutMultiColumnFlowThread::willBeRemovedFromTree()
635 // Detach all column sets from the flow thread. Cannot destroy them at this point, since they
636 // are siblings of this object, and there may be pointers to this object's sibling somewhere
637 // further up on the call stack.
638 for (LayoutMultiColumnSet
* columnSet
= firstMultiColumnSet(); columnSet
; columnSet
= columnSet
->nextSiblingMultiColumnSet())
639 columnSet
->detachFromFlowThread();
640 multiColumnBlockFlow()->resetMultiColumnFlowThread();
641 LayoutFlowThread::willBeRemovedFromTree();
644 void LayoutMultiColumnFlowThread::skipColumnSpanner(LayoutBox
* layoutObject
, LayoutUnit logicalTopInFlowThread
)
646 ASSERT(layoutObject
->isColumnSpanAll());
647 LayoutMultiColumnSpannerPlaceholder
* placeholder
= layoutObject
->spannerPlaceholder();
648 LayoutBox
* previousColumnBox
= placeholder
->previousSiblingMultiColumnBox();
649 if (previousColumnBox
&& previousColumnBox
->isLayoutMultiColumnSet()) {
650 LayoutMultiColumnSet
* columnSet
= toLayoutMultiColumnSet(previousColumnBox
);
651 if (logicalTopInFlowThread
< columnSet
->logicalTopInFlowThread())
652 logicalTopInFlowThread
= columnSet
->logicalTopInFlowThread(); // Negative margins may cause this.
653 columnSet
->endFlow(logicalTopInFlowThread
);
655 LayoutBox
* nextColumnBox
= placeholder
->nextSiblingMultiColumnBox();
656 if (nextColumnBox
&& nextColumnBox
->isLayoutMultiColumnSet()) {
657 LayoutMultiColumnSet
* nextSet
= toLayoutMultiColumnSet(nextColumnBox
);
658 m_lastSetWorkedOn
= nextSet
;
659 nextSet
->beginFlow(logicalTopInFlowThread
);
662 // We'll lay out of spanners after flow thread layout has finished (during layout of the spanner
663 // placeholders). There may be containing blocks for out-of-flow positioned descendants of the
664 // spanner in the flow thread, so that out-of-flow objects inside the spanner will be laid out
665 // as part of flow thread layout (even if the spanner itself won't). We need to add such
666 // out-of-flow positioned objects to their containing blocks now, or they'll never get laid
667 // out. Since it's non-trivial to determine if we need this, and where such out-of-flow objects
668 // might be, just go through the whole subtree.
669 for (LayoutObject
* descendant
= layoutObject
->slowFirstChild(); descendant
; descendant
= descendant
->nextInPreOrder()) {
670 if (descendant
->isBox() && descendant
->isOutOfFlowPositioned())
671 descendant
->containingBlock()->insertPositionedObject(toLayoutBox(descendant
));
675 // When processing layout objects to remove or when processing layout objects that have just been
676 // inserted, certain types of objects should be skipped.
677 static bool shouldSkipInsertedOrRemovedChild(LayoutMultiColumnFlowThread
* flowThread
, const LayoutObject
& child
)
679 if (child
.isSVG() && !child
.isSVGRoot()) {
680 // Don't descend into SVG objects. What's in there is of no interest, and there might even
681 // be a foreignObject there with column-span:all, which doesn't apply to us.
684 if (child
.isLayoutFlowThread()) {
685 // Found an inner flow thread. We need to skip it and its descendants.
688 if (child
.isLayoutMultiColumnSet() || child
.isLayoutMultiColumnSpannerPlaceholder()) {
689 // Column sets and spanner placeholders in a child multicol context don't affect the parent
693 if (child
.isOutOfFlowPositioned() && child
.containingBlock()->flowThreadContainingBlock() != flowThread
) {
694 // Out-of-flow with its containing block on the outside of the multicol container.
700 void LayoutMultiColumnFlowThread::flowThreadDescendantWasInserted(LayoutObject
* descendant
)
702 ASSERT(!m_isBeingEvacuated
);
703 // This method ensures that the list of column sets and spanner placeholders reflects the
704 // multicol content after having inserted a descendant (or descendant subtree). See the header
705 // file for more information. Go through the subtree that was just inserted and create column
706 // sets (needed by regular column content) and spanner placeholders (one needed by each spanner)
708 if (shouldSkipInsertedOrRemovedChild(this, *descendant
))
710 LayoutObject
* objectAfterSubtree
= nextInPreOrderAfterChildrenSkippingOutOfFlow(this, descendant
);
712 for (LayoutObject
* layoutObject
= descendant
; layoutObject
; layoutObject
= next
) {
713 if (layoutObject
!= descendant
&& shouldSkipInsertedOrRemovedChild(this, *layoutObject
)) {
714 next
= layoutObject
->nextInPreOrderAfterChildren(descendant
);
717 next
= layoutObject
->nextInPreOrder(descendant
);
718 if (containingColumnSpannerPlaceholder(layoutObject
))
719 continue; // Inside a column spanner. Nothing to do, then.
720 if (descendantIsValidColumnSpanner(layoutObject
)) {
721 // This layoutObject is a spanner, so it needs to establish a spanner placeholder.
722 createAndInsertSpannerPlaceholder(toLayoutBox(layoutObject
), objectAfterSubtree
);
725 // This layoutObject is regular column content (i.e. not a spanner). Create a set if necessary.
726 if (objectAfterSubtree
) {
727 if (LayoutMultiColumnSpannerPlaceholder
* placeholder
= objectAfterSubtree
->spannerPlaceholder()) {
728 // If inserted right before a spanner, we need to make sure that there's a set for us there.
729 LayoutBox
* previous
= placeholder
->previousSiblingMultiColumnBox();
730 if (!previous
|| !previous
->isLayoutMultiColumnSet())
731 createAndInsertMultiColumnSet(placeholder
);
733 // Otherwise, since |objectAfterSubtree| isn't a spanner, it has to mean that there's
734 // already a set for that content. We can use it for this layoutObject too.
735 ASSERT(mapDescendantToColumnSet(objectAfterSubtree
));
736 ASSERT(mapDescendantToColumnSet(layoutObject
) == mapDescendantToColumnSet(objectAfterSubtree
));
739 // Inserting at the end. Then we just need to make sure that there's a column set at the end.
740 LayoutBox
* lastColumnBox
= lastMultiColumnBox();
741 if (!lastColumnBox
|| !lastColumnBox
->isLayoutMultiColumnSet())
742 createAndInsertMultiColumnSet();
747 void LayoutMultiColumnFlowThread::flowThreadDescendantWillBeRemoved(LayoutObject
* descendant
)
749 // This method ensures that the list of column sets and spanner placeholders reflects the
750 // multicol content that we'll be left with after removal of a descendant (or descendant
751 // subtree). See the header file for more information. Removing content may mean that we need to
752 // remove column sets and/or spanner placeholders.
753 if (m_isBeingEvacuated
)
755 if (shouldSkipInsertedOrRemovedChild(this, *descendant
))
757 bool hadContainingPlaceholder
= containingColumnSpannerPlaceholder(descendant
);
758 bool processedSomething
= false;
760 // Remove spanner placeholders that are no longer needed, and merge column sets around them.
761 for (LayoutObject
* layoutObject
= descendant
; layoutObject
; layoutObject
= next
) {
762 if (layoutObject
!= descendant
&& shouldSkipInsertedOrRemovedChild(this, *layoutObject
)) {
763 next
= layoutObject
->nextInPreOrderAfterChildren(descendant
);
766 processedSomething
= true;
767 LayoutMultiColumnSpannerPlaceholder
* placeholder
= layoutObject
->spannerPlaceholder();
769 next
= layoutObject
->nextInPreOrder(descendant
);
772 next
= layoutObject
->nextInPreOrderAfterChildren(descendant
); // It's a spanner. Its children are of no interest to us.
773 destroySpannerPlaceholder(placeholder
);
775 if (hadContainingPlaceholder
|| !processedSomething
)
776 return; // No column content will be removed, so we can stop here.
778 // Column content will be removed. Does this mean that we should destroy a column set?
779 LayoutMultiColumnSpannerPlaceholder
* adjacentPreviousSpannerPlaceholder
= nullptr;
780 LayoutObject
* previousLayoutObject
= previousInPreOrderSkippingOutOfFlow(this, descendant
);
781 if (previousLayoutObject
&& previousLayoutObject
!= this) {
782 adjacentPreviousSpannerPlaceholder
= containingColumnSpannerPlaceholder(previousLayoutObject
);
783 if (!adjacentPreviousSpannerPlaceholder
)
784 return; // Preceded by column content. Set still needed.
786 LayoutMultiColumnSpannerPlaceholder
* adjacentNextSpannerPlaceholder
= nullptr;
787 LayoutObject
* nextLayoutObject
= nextInPreOrderAfterChildrenSkippingOutOfFlow(this, descendant
);
788 if (nextLayoutObject
) {
789 adjacentNextSpannerPlaceholder
= containingColumnSpannerPlaceholder(nextLayoutObject
);
790 if (!adjacentNextSpannerPlaceholder
)
791 return; // Followed by column content. Set still needed.
793 // We have now determined that, with the removal of |descendant|, we should remove a column
794 // set. Locate it and remove it. Do it without involving mapDescendantToColumnSet(), as that
795 // might be very slow. Deduce the right set from the spanner placeholders that we've already
797 LayoutMultiColumnSet
* columnSetToRemove
;
798 if (adjacentNextSpannerPlaceholder
) {
799 columnSetToRemove
= toLayoutMultiColumnSet(adjacentNextSpannerPlaceholder
->previousSiblingMultiColumnBox());
800 ASSERT(!adjacentPreviousSpannerPlaceholder
|| columnSetToRemove
== adjacentPreviousSpannerPlaceholder
->nextSiblingMultiColumnBox());
801 } else if (adjacentPreviousSpannerPlaceholder
) {
802 columnSetToRemove
= toLayoutMultiColumnSet(adjacentPreviousSpannerPlaceholder
->nextSiblingMultiColumnBox());
804 // If there were no adjacent spanners, it has to mean that there's only one column set,
805 // since it's only spanners that may cause creation of multiple sets.
806 columnSetToRemove
= firstMultiColumnSet();
807 ASSERT(columnSetToRemove
);
808 ASSERT(!columnSetToRemove
->nextSiblingMultiColumnSet());
810 ASSERT(columnSetToRemove
);
811 columnSetToRemove
->destroy();
814 static inline bool needsToReinsertIntoFlowThread(const ComputedStyle
& oldStyle
, const ComputedStyle
& newStyle
)
816 // If we've become (or are about to become) a container for absolutely positioned descendants,
817 // or if we're no longer going to be one, we need to re-evaluate the need for column
818 // sets. There may be out-of-flow descendants further down that become part of the flow thread,
819 // or cease to be part of the flow thread, because of this change.
820 if (oldStyle
.hasTransformRelatedProperty() != newStyle
.hasTransformRelatedProperty())
822 return (oldStyle
.hasInFlowPosition() && newStyle
.position() == StaticPosition
)
823 || (newStyle
.hasInFlowPosition() && oldStyle
.position() == StaticPosition
);
826 static inline bool needsToRemoveFromFlowThread(const ComputedStyle
& oldStyle
, const ComputedStyle
& newStyle
)
828 // If an in-flow descendant goes out-of-flow, we may have to remove column sets and spanner placeholders.
829 return (newStyle
.hasOutOfFlowPosition() && !oldStyle
.hasOutOfFlowPosition()) || needsToReinsertIntoFlowThread(oldStyle
, newStyle
);
832 static inline bool needsToInsertIntoFlowThread(const ComputedStyle
& oldStyle
, const ComputedStyle
& newStyle
)
834 // If an out-of-flow descendant goes in-flow, we may have to insert column sets and spanner placeholders.
835 return (!newStyle
.hasOutOfFlowPosition() && oldStyle
.hasOutOfFlowPosition()) || needsToReinsertIntoFlowThread(oldStyle
, newStyle
);
838 void LayoutMultiColumnFlowThread::flowThreadDescendantStyleWillChange(LayoutBox
* descendant
, StyleDifference diff
, const ComputedStyle
& newStyle
)
840 if (needsToRemoveFromFlowThread(descendant
->styleRef(), newStyle
))
841 flowThreadDescendantWillBeRemoved(descendant
);
844 void LayoutMultiColumnFlowThread::flowThreadDescendantStyleDidChange(LayoutBox
* descendant
, StyleDifference diff
, const ComputedStyle
& oldStyle
)
846 if (needsToInsertIntoFlowThread(oldStyle
, descendant
->styleRef())) {
847 flowThreadDescendantWasInserted(descendant
);
850 if (descendantIsValidColumnSpanner(descendant
)) {
851 // We went from being regular column content to becoming a spanner.
852 ASSERT(!descendant
->spannerPlaceholder());
854 // First remove this as regular column content. Note that this will walk the entire subtree
855 // of |descendant|. There might be spanners there (which won't be spanners anymore, since
856 // we're not allowed to nest spanners), whose placeholders must die.
857 flowThreadDescendantWillBeRemoved(descendant
);
859 createAndInsertSpannerPlaceholder(descendant
, nextInPreOrderAfterChildrenSkippingOutOfFlow(this, descendant
));
863 void LayoutMultiColumnFlowThread::computePreferredLogicalWidths()
865 LayoutFlowThread::computePreferredLogicalWidths();
867 // The min/max intrinsic widths calculated really tell how much space elements need when
868 // laid out inside the columns. In order to eventually end up with the desired column width,
869 // we need to convert them to values pertaining to the multicol container.
870 const LayoutBlockFlow
* multicolContainer
= multiColumnBlockFlow();
871 const ComputedStyle
* multicolStyle
= multicolContainer
->style();
872 int columnCount
= multicolStyle
->hasAutoColumnCount() ? 1 : multicolStyle
->columnCount();
873 LayoutUnit columnWidth
;
874 LayoutUnit gapExtra
= (columnCount
- 1) * multicolContainer
->columnGap();
875 if (multicolStyle
->hasAutoColumnWidth()) {
876 m_minPreferredLogicalWidth
= m_minPreferredLogicalWidth
* columnCount
+ gapExtra
;
878 columnWidth
= multicolStyle
->columnWidth();
879 m_minPreferredLogicalWidth
= std::min(m_minPreferredLogicalWidth
, columnWidth
);
881 // Note that if column-count is auto here, we should resolve it to calculate the maximum
882 // intrinsic width, instead of pretending that it's 1. The only way to do that is by performing
883 // a layout pass, but this is not an appropriate time or place for layout. The good news is that
884 // if height is unconstrained and there are no explicit breaks, the resolved column-count really
886 m_maxPreferredLogicalWidth
= std::max(m_maxPreferredLogicalWidth
, columnWidth
) * columnCount
+ gapExtra
;
889 void LayoutMultiColumnFlowThread::computeLogicalHeight(LayoutUnit logicalHeight
, LayoutUnit logicalTop
, LogicalExtentComputedValues
& computedValues
) const
891 // We simply remain at our intrinsic height.
892 computedValues
.m_extent
= logicalHeight
;
893 computedValues
.m_position
= logicalTop
;
896 void LayoutMultiColumnFlowThread::updateLogicalWidth()
898 LayoutUnit columnWidth
;
899 calculateColumnCountAndWidth(columnWidth
, m_columnCount
);
900 setLogicalWidth(columnWidth
);
903 void LayoutMultiColumnFlowThread::layout()
905 ASSERT(!m_lastSetWorkedOn
);
906 m_lastSetWorkedOn
= firstMultiColumnSet();
907 if (m_lastSetWorkedOn
)
908 m_lastSetWorkedOn
->beginFlow(LayoutUnit());
909 LayoutFlowThread::layout();
910 if (LayoutMultiColumnSet
* lastSet
= lastMultiColumnSet()) {
911 ASSERT(lastSet
== m_lastSetWorkedOn
);
912 if (!lastSet
->nextSiblingMultiColumnBox()) {
913 // Include trailing overflow in the last column set. The idea is that we will generate
914 // additional columns and pages to hold that overflow, since people do write bad content
915 // like <body style="height:0px"> in multi-column layouts.
916 // TODO(mstensho): Once we support nested multicol, adding in overflow here may result
917 // in the need for creating additional rows, since there may not be enough space
918 // remaining in the currently last row.
919 LayoutRect layoutRect
= layoutOverflowRect();
920 LayoutUnit logicalBottomInFlowThread
= isHorizontalWritingMode() ? layoutRect
.maxY() : layoutRect
.maxX();
921 ASSERT(logicalBottomInFlowThread
>= logicalHeight());
922 lastSet
->endFlow(logicalBottomInFlowThread
);
925 m_lastSetWorkedOn
= nullptr;
928 void LayoutMultiColumnFlowThread::setPageBreak(LayoutUnit offset
, LayoutUnit spaceShortage
)
930 // Only positive values are interesting (and allowed) here. Zero space shortage may be reported
931 // when we're at the top of a column and the element has zero height. Ignore this, and also
932 // ignore any negative values, which may occur when we set an early break in order to honor
933 // widows in the next column.
934 if (spaceShortage
<= 0)
937 if (LayoutMultiColumnSet
* multicolSet
= columnSetAtBlockOffset(offset
))
938 multicolSet
->recordSpaceShortage(offset
, spaceShortage
);
941 void LayoutMultiColumnFlowThread::updateMinimumPageHeight(LayoutUnit offset
, LayoutUnit minHeight
)
943 if (LayoutMultiColumnSet
* multicolSet
= columnSetAtBlockOffset(offset
))
944 multicolSet
->updateMinimumColumnHeight(offset
, minHeight
);
947 bool LayoutMultiColumnFlowThread::addForcedColumnBreak(LayoutUnit offset
, LayoutObject
* /*breakChild*/, bool /*isBefore*/, LayoutUnit
* offsetBreakAdjustment
)
949 if (LayoutMultiColumnSet
* multicolSet
= columnSetAtBlockOffset(offset
)) {
950 multicolSet
->addContentRun(offset
);
951 if (offsetBreakAdjustment
)
952 *offsetBreakAdjustment
= pageLogicalHeightForOffset(offset
) ? pageRemainingLogicalHeightForOffset(offset
, AssociateWithFormerPage
) : LayoutUnit();