Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (C) 2012 Apple Inc. All rights reserved. | 2 * Copyright (C) 2012 Apple Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
| 6 * are met: | 6 * are met: |
| 7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
| 8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
| 9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
| 10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
| 11 * documentation and/or other materials provided with the distribution. | 11 * documentation and/or other materials provided with the distribution. |
| 12 * | 12 * |
| 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 * PROFITS; OR BUSINESS IN..0TERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 20 * PROFITS; OR BUSINESS IN..0TERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 */ | 24 */ |
| 25 | 25 |
| 26 #include "config.h" | 26 #include "config.h" |
| 27 #include "core/rendering/RenderMultiColumnFlowThread.h" | 27 #include "core/rendering/RenderMultiColumnFlowThread.h" |
| 28 | 28 |
| 29 #include "core/rendering/RenderMultiColumnSet.h" | 29 #include "core/rendering/RenderMultiColumnSet.h" |
| 30 #include "core/rendering/RenderMultiColumnSpannerPlaceholder.h" | |
| 30 | 31 |
| 31 namespace blink { | 32 namespace blink { |
| 32 | 33 |
| 33 RenderMultiColumnFlowThread::RenderMultiColumnFlowThread() | 34 RenderMultiColumnFlowThread::RenderMultiColumnFlowThread() |
| 34 : m_columnCount(1) | 35 : m_lastSetWorkedOn(0) |
| 36 , m_columnCount(1) | |
| 35 , m_columnHeightAvailable(0) | 37 , m_columnHeightAvailable(0) |
| 38 , m_inLayout(false) | |
| 36 , m_inBalancingPass(false) | 39 , m_inBalancingPass(false) |
| 37 , m_needsColumnHeightsRecalculation(false) | 40 , m_needsColumnHeightsRecalculation(false) |
| 38 , m_progressionIsInline(true) | 41 , m_progressionIsInline(true) |
| 42 , m_beingEvacuated(false) | |
| 39 { | 43 { |
| 40 setFlowThreadState(InsideInFlowThread); | 44 setFlowThreadState(InsideInFlowThread); |
| 41 } | 45 } |
| 42 | 46 |
| 43 RenderMultiColumnFlowThread::~RenderMultiColumnFlowThread() | 47 RenderMultiColumnFlowThread::~RenderMultiColumnFlowThread() |
| 44 { | 48 { |
| 45 } | 49 } |
| 46 | 50 |
| 47 RenderMultiColumnFlowThread* RenderMultiColumnFlowThread::createAnonymous(Docume nt& document, RenderStyle* parentStyle) | 51 RenderMultiColumnFlowThread* RenderMultiColumnFlowThread::createAnonymous(Docume nt& document, RenderStyle* parentStyle) |
| 48 { | 52 { |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 63 | 67 |
| 64 RenderMultiColumnSet* RenderMultiColumnFlowThread::lastMultiColumnSet() const | 68 RenderMultiColumnSet* RenderMultiColumnFlowThread::lastMultiColumnSet() const |
| 65 { | 69 { |
| 66 for (RenderObject* sibling = multiColumnBlockFlow()->lastChild(); sibling; s ibling = sibling->previousSibling()) { | 70 for (RenderObject* sibling = multiColumnBlockFlow()->lastChild(); sibling; s ibling = sibling->previousSibling()) { |
| 67 if (sibling->isRenderMultiColumnSet()) | 71 if (sibling->isRenderMultiColumnSet()) |
| 68 return toRenderMultiColumnSet(sibling); | 72 return toRenderMultiColumnSet(sibling); |
| 69 } | 73 } |
| 70 return 0; | 74 return 0; |
| 71 } | 75 } |
| 72 | 76 |
| 73 void RenderMultiColumnFlowThread::addChild(RenderObject* newChild, RenderObject* beforeChild) | 77 RenderBox* RenderMultiColumnFlowThread::firstColumnSetOrSpanner() const |
| 74 { | 78 { |
| 75 RenderBlockFlow::addChild(newChild, beforeChild); | 79 if (RenderObject* sibling = nextSibling()) { |
| 76 if (firstMultiColumnSet()) | 80 ASSERT(sibling->isBox()); |
| 77 return; | 81 ASSERT(sibling->isRenderMultiColumnSet() || findColumnSpannerPlaceholder (toRenderBox(sibling))); |
| 82 return toRenderBox(sibling); | |
| 83 } | |
| 84 return 0; | |
| 85 } | |
| 78 | 86 |
| 79 // For now we only create one column set. It's created as soon as the multic ol container gets | 87 RenderBox* RenderMultiColumnFlowThread::nextColumnSetOrSpannerSiblingOf(const Re nderBox* child) |
| 80 // any content at all. | 88 { |
| 81 RenderMultiColumnSet* newSet = RenderMultiColumnSet::createAnonymous(this, m ultiColumnBlockFlow()->style()); | 89 if (!child) |
| 90 return 0; | |
| 91 if (RenderObject* sibling = child->nextSibling()) { | |
| 92 ASSERT(sibling->isBox()); | |
| 93 return toRenderBox(sibling); | |
| 94 } | |
| 95 return 0; | |
| 96 } | |
| 82 | 97 |
| 83 // Need to skip RenderBlockFlow's implementation of addChild(), or we'd get redirected right | 98 RenderBox* RenderMultiColumnFlowThread::previousColumnSetOrSpannerSiblingOf(cons t RenderBox* child) |
| 84 // back here. | 99 { |
| 85 multiColumnBlockFlow()->RenderBlock::addChild(newSet); | 100 if (!child) |
| 101 return 0; | |
| 102 if (RenderObject* sibling = child->previousSibling()) { | |
| 103 ASSERT(sibling->isBox()); | |
| 104 if (sibling->isRenderFlowThread()) | |
| 105 return 0; | |
| 106 return toRenderBox(sibling); | |
| 107 } | |
| 108 return 0; | |
| 109 } | |
| 86 | 110 |
| 87 invalidateRegions(); | 111 RenderMultiColumnSet* RenderMultiColumnFlowThread::findSetRendering(RenderObject * renderer) const |
| 112 { | |
| 113 RenderMultiColumnSet* multicolSet = firstMultiColumnSet(); | |
| 114 if (!multicolSet) | |
| 115 return 0; | |
| 116 | |
| 117 if (!multicolSet->nextSiblingMultiColumnSet()) { | |
| 118 // There is only one set. This is easy, then: if it's in the flow thread , it's part of the set. | |
| 119 return renderer->isDescendantOf(this) ? multicolSet : 0; | |
| 120 } | |
| 121 | |
| 122 // This is SLOW! But luckily very uncommon. You need to dynamically insert a spanner into the | |
| 123 // middle of the tree to trigger this. | |
| 124 for (; multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) { | |
| 125 RenderObject* startRenderer; | |
| 126 if (RenderBox* sibling = previousColumnSetOrSpannerSiblingOf(multicolSet )) { | |
| 127 // Adjacent sets should not occur. Currently we would have no way of figuring out what each | |
| 128 // of them contains then. | |
| 129 ASSERT(!sibling->isRenderMultiColumnSet()); | |
| 130 startRenderer = findColumnSpannerPlaceholder(sibling)->nextInPreOrde rAfterChildren(this); | |
| 131 } else { | |
| 132 startRenderer = firstChild(); | |
| 133 } | |
| 134 ASSERT(startRenderer); | |
| 135 | |
| 136 RenderObject* stopRenderer; | |
| 137 if (RenderBox* sibling = nextColumnSetOrSpannerSiblingOf(multicolSet)) { | |
| 138 // Adjacent sets should not occur. Currently we would have no way of figuring out what each | |
| 139 // of them contains then. | |
| 140 ASSERT(!sibling->isRenderMultiColumnSet()); | |
| 141 stopRenderer = findColumnSpannerPlaceholder(sibling); | |
| 142 } else { | |
| 143 stopRenderer = 0; | |
|
rune
2014/08/27 09:23:44
The code in lastRendererInFlowThread() used flowTh
mstensho (USE GERRIT)
2014/08/27 10:12:36
No, because the stayWithin parameter to nextInPreO
rune
2014/08/27 10:22:27
Acknowledged.
| |
| 144 } | |
| 145 | |
| 146 for (RenderObject* walker = startRenderer; walker != stopRenderer; walke r = walker->nextInPreOrder(this)) { | |
| 147 if (walker == renderer) | |
| 148 return multicolSet; | |
| 149 } | |
| 150 } | |
| 151 | |
| 152 return 0; | |
| 88 } | 153 } |
| 89 | 154 |
| 90 void RenderMultiColumnFlowThread::populate() | 155 void RenderMultiColumnFlowThread::populate() |
| 91 { | 156 { |
| 92 RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); | 157 RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); |
| 93 ASSERT(!nextSibling()); | 158 ASSERT(!nextSibling()); |
| 94 // Reparent children preceding the flow thread into the flow thread. It's mu lticol content | 159 // Reparent children preceding the flow thread into the flow thread. It's mu lticol content |
| 95 // now. At this point there's obviously nothing after the flow thread, but r enderers (column | 160 // now. At this point there's obviously nothing after the flow thread, but r enderers (column |
| 96 // sets and spanners) will be inserted there as we insert elements into the flow thread. | 161 // sets and spanners) will be inserted there as we insert elements into the flow thread. |
| 97 multicolContainer->moveChildrenTo(this, multicolContainer->firstChild(), thi s, true); | 162 multicolContainer->moveChildrenTo(this, multicolContainer->firstChild(), thi s, true); |
| 98 } | 163 } |
| 99 | 164 |
| 100 void RenderMultiColumnFlowThread::evacuateAndDestroy() | 165 void RenderMultiColumnFlowThread::evacuateAndDestroy() |
| 101 { | 166 { |
| 102 RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); | 167 RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); |
| 168 m_beingEvacuated = true; | |
| 169 | |
| 170 // First promote all children of the flow thread. Before we move them to the flow thread's | |
| 171 // container, we need to unregister the flow thread, so that they aren't jus t re-added again to | |
| 172 // the flow thread that we're trying to empty. | |
| 173 multicolContainer->resetMultiColumnFlowThread(); | |
| 174 moveAllChildrenTo(multicolContainer, true); | |
| 175 | |
| 176 // Move spanners back to their original DOM position in the tree, and destro y the placeholders. | |
|
Julien - ping for review
2014/09/03 23:22:23
This is a big concern that we don't respect the DO
mstensho (USE GERRIT)
2014/09/04 12:14:02
I understand, and I admit that this is easily the
Julien - ping for review
2014/09/04 17:47:24
It makes me very nervous. The reason is that all c
mstensho (USE GERRIT)
2014/09/10 21:40:53
The answer is here (well, maybe): :)
https://coder
| |
| 177 SpannerMap::iterator it; | |
| 178 while ((it = m_spannerMap.begin()) != m_spannerMap.end()) { | |
| 179 RenderBox* spanner = it->key; | |
| 180 RenderMultiColumnSpannerPlaceholder* placeholder = it->value; | |
| 181 RenderBlockFlow* originalContainer = toRenderBlockFlow(placeholder->pare nt()); | |
| 182 multicolContainer->removeChild(spanner); | |
| 183 originalContainer->addChild(spanner, placeholder); | |
| 184 placeholder->destroy(); | |
| 185 m_spannerMap.remove(it); | |
| 186 } | |
| 103 | 187 |
| 104 // Remove all sets. | 188 // Remove all sets. |
| 105 while (RenderMultiColumnSet* columnSet = firstMultiColumnSet()) | 189 while (RenderMultiColumnSet* columnSet = firstMultiColumnSet()) |
| 106 columnSet->destroy(); | 190 columnSet->destroy(); |
| 107 | 191 |
| 108 ASSERT(!previousSibling()); | |
| 109 ASSERT(!nextSibling()); | |
| 110 | |
| 111 // Finally we can promote all flow thread's children. Before we move them to the flow thread's | |
| 112 // container, we need to unregister the flow thread, so that they aren't jus t re-added again to | |
| 113 // the flow thread that we're trying to empty. | |
| 114 multicolContainer->resetMultiColumnFlowThread(); | |
| 115 moveAllChildrenTo(multicolContainer, true); | |
| 116 | |
| 117 // FIXME: it's scary that neither destroy() nor the move*Children* methods t ake care of this, | 192 // FIXME: it's scary that neither destroy() nor the move*Children* methods t ake care of this, |
| 118 // and instead leave you with dangling root line box pointers. But since thi s is how it is done | 193 // and instead leave you with dangling root line box pointers. But since thi s is how it is done |
| 119 // in other parts of the code that deal with reparenting renderers, let's do the cleanup on our | 194 // in other parts of the code that deal with reparenting renderers, let's do the cleanup on our |
| 120 // own here as well. | 195 // own here as well. |
| 121 deleteLineBoxTree(); | 196 deleteLineBoxTree(); |
| 122 | 197 |
| 123 destroy(); | 198 destroy(); |
| 124 } | 199 } |
| 125 | 200 |
| 126 LayoutSize RenderMultiColumnFlowThread::columnOffset(const LayoutPoint& point) c onst | 201 LayoutSize RenderMultiColumnFlowThread::columnOffset(const LayoutPoint& point) c onst |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 160 m_needsColumnHeightsRecalculation = false; | 235 m_needsColumnHeightsRecalculation = false; |
| 161 return; | 236 return; |
| 162 } | 237 } |
| 163 | 238 |
| 164 for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; col umnSet = columnSet->nextSiblingMultiColumnSet()) { | 239 for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; col umnSet = columnSet->nextSiblingMultiColumnSet()) { |
| 165 if (!m_inBalancingPass) { | 240 if (!m_inBalancingPass) { |
| 166 // This is the initial layout pass. We need to reset the column heig ht, because contents | 241 // This is the initial layout pass. We need to reset the column heig ht, because contents |
| 167 // typically have changed. | 242 // typically have changed. |
| 168 columnSet->resetColumnHeight(); | 243 columnSet->resetColumnHeight(); |
| 169 } | 244 } |
| 245 columnSet->resetFlow(); | |
| 170 } | 246 } |
| 171 | 247 |
| 172 invalidateRegions(); | 248 invalidateRegions(); |
| 173 m_needsColumnHeightsRecalculation = heightIsAuto(); | 249 m_needsColumnHeightsRecalculation = true; |
| 174 layout(); | 250 layout(); |
| 175 } | 251 } |
| 176 | 252 |
| 177 bool RenderMultiColumnFlowThread::recalculateColumnHeights() | 253 bool RenderMultiColumnFlowThread::recalculateColumnHeights() |
| 178 { | 254 { |
| 179 // All column sets that needed layout have now been laid out, so we can fina lly validate them. | 255 // All column sets that needed layout have now been laid out, so we can fina lly validate them. |
| 180 validateRegions(); | 256 validateRegions(); |
| 181 | 257 |
| 182 if (!m_needsColumnHeightsRecalculation) | 258 if (!m_needsColumnHeightsRecalculation) |
| 183 return false; | 259 return false; |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 199 } | 275 } |
| 200 } | 276 } |
| 201 | 277 |
| 202 if (needsRelayout) | 278 if (needsRelayout) |
| 203 setChildNeedsLayout(MarkOnlyThis); | 279 setChildNeedsLayout(MarkOnlyThis); |
| 204 | 280 |
| 205 m_inBalancingPass = needsRelayout; | 281 m_inBalancingPass = needsRelayout; |
| 206 return needsRelayout; | 282 return needsRelayout; |
| 207 } | 283 } |
| 208 | 284 |
| 285 void RenderMultiColumnFlowThread::advanceToNextColumnSet(RenderMultiColumnSpanne rPlaceholder* placeholder) | |
| 286 { | |
| 287 LayoutUnit logicalBottomInFlowThread = placeholder->offsetFromLogicalTopOfFi rstPage(); | |
| 288 for (RenderBox* prev = previousColumnSetOrSpannerSiblingOf(placeholder->span ner()); prev; prev = previousColumnSetOrSpannerSiblingOf(prev)) { | |
| 289 if (prev->isRenderMultiColumnSet()) { | |
|
Julien - ping for review
2014/09/03 23:22:23
Nit: We usually prefer early return / break.
mstensho (USE GERRIT)
2014/09/04 12:14:02
Done.
| |
| 290 toRenderMultiColumnSet(prev)->endFlow(logicalBottomInFlowThread); | |
| 291 break; | |
| 292 } | |
| 293 } | |
| 294 | |
| 295 for (RenderBox* next = nextColumnSetOrSpannerSiblingOf(placeholder->spanner( )); next; next = nextColumnSetOrSpannerSiblingOf(next)) { | |
| 296 if (next->isRenderMultiColumnSet()) { | |
|
Julien - ping for review
2014/09/03 23:22:23
Same comment.
mstensho (USE GERRIT)
2014/09/04 12:14:02
Done.
| |
| 297 m_lastSetWorkedOn = toRenderMultiColumnSet(next); | |
| 298 m_lastSetWorkedOn->beginFlow(logicalBottomInFlowThread); | |
| 299 break; | |
| 300 } | |
| 301 } | |
| 302 } | |
| 303 | |
| 209 void RenderMultiColumnFlowThread::calculateColumnCountAndWidth(LayoutUnit& width , unsigned& count) const | 304 void RenderMultiColumnFlowThread::calculateColumnCountAndWidth(LayoutUnit& width , unsigned& count) const |
| 210 { | 305 { |
| 211 RenderBlock* columnBlock = multiColumnBlockFlow(); | 306 RenderBlock* columnBlock = multiColumnBlockFlow(); |
| 212 const RenderStyle* columnStyle = columnBlock->style(); | 307 const RenderStyle* columnStyle = columnBlock->style(); |
| 213 LayoutUnit availableWidth = columnBlock->contentLogicalWidth(); | 308 LayoutUnit availableWidth = columnBlock->contentLogicalWidth(); |
| 214 LayoutUnit columnGap = columnBlock->columnGap(); | 309 LayoutUnit columnGap = columnBlock->columnGap(); |
| 215 LayoutUnit computedColumnWidth = max<LayoutUnit>(1, LayoutUnit(columnStyle-> columnWidth())); | 310 LayoutUnit computedColumnWidth = max<LayoutUnit>(1, LayoutUnit(columnStyle-> columnWidth())); |
| 216 unsigned computedColumnCount = max<int>(1, columnStyle->columnCount()); | 311 unsigned computedColumnCount = max<int>(1, columnStyle->columnCount()); |
| 217 | 312 |
| 218 ASSERT(!columnStyle->hasAutoColumnCount() || !columnStyle->hasAutoColumnWidt h()); | 313 ASSERT(!columnStyle->hasAutoColumnCount() || !columnStyle->hasAutoColumnWidt h()); |
| 219 if (columnStyle->hasAutoColumnWidth() && !columnStyle->hasAutoColumnCount()) { | 314 if (columnStyle->hasAutoColumnWidth() && !columnStyle->hasAutoColumnCount()) { |
| 220 count = computedColumnCount; | 315 count = computedColumnCount; |
| 221 width = std::max<LayoutUnit>(0, (availableWidth - ((count - 1) * columnG ap)) / count); | 316 width = std::max<LayoutUnit>(0, (availableWidth - ((count - 1) * columnG ap)) / count); |
| 222 } else if (!columnStyle->hasAutoColumnWidth() && columnStyle->hasAutoColumnC ount()) { | 317 } else if (!columnStyle->hasAutoColumnWidth() && columnStyle->hasAutoColumnC ount()) { |
| 223 count = std::max<LayoutUnit>(1, (availableWidth + columnGap) / (computed ColumnWidth + columnGap)); | 318 count = std::max<LayoutUnit>(1, (availableWidth + columnGap) / (computed ColumnWidth + columnGap)); |
| 224 width = ((availableWidth + columnGap) / count) - columnGap; | 319 width = ((availableWidth + columnGap) / count) - columnGap; |
| 225 } else { | 320 } else { |
| 226 count = std::max<LayoutUnit>(std::min<LayoutUnit>(computedColumnCount, ( availableWidth + columnGap) / (computedColumnWidth + columnGap)), 1); | 321 count = std::max<LayoutUnit>(std::min<LayoutUnit>(computedColumnCount, ( availableWidth + columnGap) / (computedColumnWidth + columnGap)), 1); |
| 227 width = ((availableWidth + columnGap) / count) - columnGap; | 322 width = ((availableWidth + columnGap) / count) - columnGap; |
| 228 } | 323 } |
| 229 } | 324 } |
| 230 | 325 |
| 326 bool RenderMultiColumnFlowThread::isDescendantValidColumnSpanner(RenderObject* d escendant) const | |
| 327 { | |
| 328 // We assume that we're inside the flow thread. This function is not to be c alled otherwise. | |
| 329 ASSERT(descendant->isDescendantOf(this)); | |
| 330 | |
| 331 // First make sure that the renderer itself has the right properties for bec oming a spanner. | |
| 332 RenderStyle* style = descendant->style(); | |
| 333 if (style->columnSpan() != ColumnSpanAll || !descendant->isBox() || descenda nt->isFloatingOrOutOfFlowPositioned()) | |
| 334 return false; | |
| 335 | |
| 336 RenderBlock* container = descendant->containingBlock(); | |
| 337 if (!container->isRenderBlockFlow() || container->childrenInline()) { | |
| 338 // Needs to be block-level. | |
| 339 return false; | |
| 340 } | |
| 341 | |
| 342 // This looks like a spanner, but if we're inside something unbreakable, it' s not to be treated as one. | |
| 343 for (RenderBox* ancestor = toRenderBox(descendant)->parentBox(); ancestor; a ncestor = ancestor->parentBox()) { | |
| 344 if (ancestor->isRenderFlowThread()) { | |
| 345 // Don't allow any intervening non-multicol fragmentation contexts. The spec doesn't say | |
| 346 // anything about disallowing this, but it's just going to be too co mplicated to | |
| 347 // implement (not to mention specify behavior). | |
| 348 return ancestor == this; | |
| 349 } | |
| 350 ASSERT(ancestor->style()->columnSpan() != ColumnSpanAll || !isDescendant ValidColumnSpanner(ancestor)); | |
| 351 if (ancestor->isUnsplittableForPagination()) | |
| 352 return false; | |
| 353 } | |
| 354 ASSERT_NOT_REACHED(); | |
| 355 return false; | |
| 356 } | |
| 357 | |
| 231 const char* RenderMultiColumnFlowThread::renderName() const | 358 const char* RenderMultiColumnFlowThread::renderName() const |
| 232 { | 359 { |
| 233 return "RenderMultiColumnFlowThread"; | 360 return "RenderMultiColumnFlowThread"; |
| 234 } | 361 } |
| 235 | 362 |
| 236 void RenderMultiColumnFlowThread::addRegionToThread(RenderMultiColumnSet* column Set) | 363 void RenderMultiColumnFlowThread::addRegionToThread(RenderMultiColumnSet* column Set) |
| 237 { | 364 { |
| 238 if (RenderMultiColumnSet* nextSet = columnSet->nextSiblingMultiColumnSet()) { | 365 if (RenderMultiColumnSet* nextSet = columnSet->nextSiblingMultiColumnSet()) { |
| 239 RenderMultiColumnSetList::iterator it = m_multiColumnSetList.find(nextSe t); | 366 RenderMultiColumnSetList::iterator it = m_multiColumnSetList.find(nextSe t); |
| 240 ASSERT(it != m_multiColumnSetList.end()); | 367 ASSERT(it != m_multiColumnSetList.end()); |
| 241 m_multiColumnSetList.insertBefore(it, columnSet); | 368 m_multiColumnSetList.insertBefore(it, columnSet); |
| 242 } else { | 369 } else { |
| 243 m_multiColumnSetList.add(columnSet); | 370 m_multiColumnSetList.add(columnSet); |
| 244 } | 371 } |
| 245 columnSet->setIsValid(true); | 372 columnSet->setIsValid(true); |
| 246 } | 373 } |
| 247 | 374 |
| 248 void RenderMultiColumnFlowThread::willBeRemovedFromTree() | 375 void RenderMultiColumnFlowThread::willBeRemovedFromTree() |
| 249 { | 376 { |
| 250 // Detach all column sets from the flow thread. Cannot destroy them at this point, since they | 377 // Detach all column sets from the flow thread. Cannot destroy them at this point, since they |
| 251 // are siblings of this object, and there may be pointers to this object's s ibling somewhere | 378 // are siblings of this object, and there may be pointers to this object's s ibling somewhere |
| 252 // further up on the call stack. | 379 // further up on the call stack. |
| 253 for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; col umnSet = columnSet->nextSiblingMultiColumnSet()) | 380 for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; col umnSet = columnSet->nextSiblingMultiColumnSet()) |
| 254 columnSet->detachRegion(); | 381 columnSet->detachRegion(); |
| 255 multiColumnBlockFlow()->resetMultiColumnFlowThread(); | 382 multiColumnBlockFlow()->resetMultiColumnFlowThread(); |
| 256 RenderFlowThread::willBeRemovedFromTree(); | 383 RenderFlowThread::willBeRemovedFromTree(); |
| 257 } | 384 } |
| 258 | 385 |
| 386 RenderObject* RenderMultiColumnFlowThread::resolveMovedChild(RenderObject* child ) const | |
| 387 { | |
| 388 if (child->style()->columnSpan() != ColumnSpanAll || !child->isBox()) { | |
| 389 // We only need to resolve for column spanners. | |
| 390 return child; | |
| 391 } | |
| 392 // The renderer for the actual DOM node that establishes a spanner is moved from its original | |
| 393 // location in the render tree to becoming a sibling of the column sets. In other words, it's | |
| 394 // moved out from the flow thread (and becomes a sibling of it). When we for instance want to | |
| 395 // create and insert a renderer for the sibling node immediately preceding t he spanner, we need | |
| 396 // to map that spanner renderer to the spanner's placeholder, which is where the new inserted | |
| 397 // renderer belongs. | |
| 398 if (RenderMultiColumnSpannerPlaceholder* placeholder = findColumnSpannerPlac eholder(toRenderBox(child))) | |
| 399 return placeholder; | |
| 400 | |
| 401 // This is an invalid spanner, or its placeholder hasn't been created yet. T his happens when | |
| 402 // moving an entire subtree into the flow thread, when we are processing the insertion of this | |
| 403 // spanner's preceding sibling, and we obviously haven't got as far as proce ssing this spanner | |
| 404 // yet. | |
| 405 return child; | |
| 406 } | |
| 407 | |
| 408 void RenderMultiColumnFlowThread::flowThreadDescendantInserted(RenderObject* des cendant) | |
| 409 { | |
| 410 if (m_beingEvacuated) | |
| 411 return; | |
| 412 RenderObject* subtreeRoot = descendant; | |
| 413 for (; descendant; descendant = descendant->nextInPreOrder(subtreeRoot)) { | |
| 414 if (descendant->isRenderMultiColumnSpannerPlaceholder()) { | |
| 415 // A spanner's placeholder has been inserted. The actual spanner ren derer is moved from | |
| 416 // where it would otherwise occur (if it weren't a spanner) to becom ing a sibling of the | |
| 417 // column sets. | |
| 418 RenderMultiColumnSpannerPlaceholder* placeholder = toRenderMultiColu mnSpannerPlaceholder(descendant); | |
| 419 ASSERT(!m_spannerMap.get(placeholder->spanner())); | |
| 420 m_spannerMap.add(placeholder->spanner(), placeholder); | |
| 421 ASSERT(!placeholder->slowFirstChild()); // There should be no childr en here, but if there are, we ought to skip them. | |
| 422 continue; | |
| 423 } | |
| 424 RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); | |
| 425 RenderObject* nextRendererInFlowThread = descendant->nextInPreOrderAfter Children(this); | |
| 426 RenderObject* insertBeforeMulticolChild = 0; | |
| 427 if (isDescendantValidColumnSpanner(descendant)) { | |
| 428 // This is a spanner (column-span:all). Such renderers are moved fro m where they would | |
| 429 // otherwise occur in the render tree to becoming a direct child of the multicol container, | |
| 430 // so that they live among the column sets. This simplifies the layo ut implementation, and | |
| 431 // basically just relies on regular block layout done by the RenderB lockFlow that | |
| 432 // establishes the multicol container. | |
| 433 RenderBlockFlow* container = toRenderBlockFlow(descendant->parent()) ; | |
| 434 RenderMultiColumnSet* setToSplit = 0; | |
| 435 if (nextRendererInFlowThread) { | |
| 436 setToSplit = findSetRendering(descendant); | |
| 437 if (setToSplit) { | |
| 438 setToSplit->setNeedsLayoutAndFullPaintInvalidation(); | |
| 439 insertBeforeMulticolChild = setToSplit->nextSibling(); | |
| 440 } | |
| 441 } | |
| 442 // Moving a spanner's renderer so that it becomes a sibling of the c olumn sets requires us | |
| 443 // to insert an anonymous placeholder in the tree where the spanner' s renderer otherwise | |
| 444 // would have been. This is needed for two reasons: We need a way of separating inline | |
| 445 // content before and after the spanner, so that it becomes separate line boxes. Secondly, | |
| 446 // this placeholder serves as a break point for column sets, so that , when encountered, we | |
| 447 // end flowing one column set and move on to the next one. | |
| 448 RenderMultiColumnSpannerPlaceholder* placeholder = RenderMultiColumn SpannerPlaceholder::createAnonymous(this, toRenderBox(descendant), container->st yle()); | |
| 449 container->addChild(placeholder, descendant->nextSibling()); | |
| 450 container->removeChild(descendant); | |
| 451 multicolContainer->RenderBlock::addChild(descendant, insertBeforeMul ticolChild); | |
| 452 | |
| 453 // The spanner has now been moved out from the flow thread, but we d on't want to | |
| 454 // examine its children anyway. They are all part of the spanner and shouldn't trigger | |
| 455 // creation of column sets or anything like that. Continue at its or iginal position in | |
| 456 // the tree, i.e. where the placeholder was just put. | |
| 457 if (subtreeRoot == descendant) | |
| 458 subtreeRoot = placeholder; | |
| 459 descendant = placeholder; | |
| 460 } else { | |
| 461 // This is regular multicol content, i.e. not part of a spanner. | |
| 462 if (nextRendererInFlowThread && nextRendererInFlowThread->isRenderMu ltiColumnSpannerPlaceholder()) { | |
| 463 // Inserted right before a spanner. Is there a set for us there? | |
| 464 RenderMultiColumnSpannerPlaceholder* placeholder = toRenderMulti ColumnSpannerPlaceholder(nextRendererInFlowThread); | |
| 465 if (RenderObject* previous = placeholder->spanner()->previousSib ling()) { | |
| 466 if (previous->isRenderMultiColumnSet()) | |
| 467 continue; // There's already a set there. Nothing to do. | |
| 468 } | |
| 469 insertBeforeMulticolChild = placeholder->spanner(); | |
| 470 } else if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) { | |
| 471 // This child is not an immediate predecessor of a spanner, whic h means that if this | |
| 472 // child precedes a spanner at all, there has to be a column set created for us there | |
| 473 // already. If it doesn't precede any spanner at all, on the oth er hand, we need a | |
| 474 // column set at the end of the multicol container. We don't rea lly check here if the | |
| 475 // child inserted precedes any spanner or not (as that's an expe nsive operation). Just | |
| 476 // make sure we have a column set at the end. It's no big deal i f it remains unused. | |
| 477 if (!lastSet->nextSibling()) | |
| 478 continue; | |
| 479 } | |
| 480 } | |
| 481 // Need to create a new column set when there's no set already created. We also always insert | |
| 482 // another column set after a spanner. Even if it turns out that there a re no renderers | |
| 483 // following the spanner, there may be bottom margins there, which take up space. | |
| 484 RenderMultiColumnSet* newSet = RenderMultiColumnSet::createAnonymous(thi s, multicolContainer->style()); | |
| 485 multicolContainer->RenderBlock::addChild(newSet, insertBeforeMulticolChi ld); | |
| 486 invalidateRegions(); | |
| 487 | |
| 488 // We cannot handle immediate column set siblings at the moment (and the re's no need for | |
| 489 // it, either). There has to be at least one spanner separating them. | |
| 490 ASSERT(!previousColumnSetOrSpannerSiblingOf(newSet) || !previousColumnSe tOrSpannerSiblingOf(newSet)->isRenderMultiColumnSet()); | |
| 491 ASSERT(!nextColumnSetOrSpannerSiblingOf(newSet) || !nextColumnSetOrSpann erSiblingOf(newSet)->isRenderMultiColumnSet()); | |
| 492 } | |
| 493 } | |
| 494 | |
| 495 void RenderMultiColumnFlowThread::flowThreadRelativeWillBeRemoved(RenderObject* relative) | |
| 496 { | |
| 497 if (m_beingEvacuated) | |
| 498 return; | |
| 499 invalidateRegions(); | |
| 500 if (relative->isRenderMultiColumnSpannerPlaceholder()) { | |
| 501 // Remove the map entry for this spanner, but leave the actual spanner r enderer alone. Also | |
| 502 // keep the reference to the spanner, since the placeholder may be about to be re-inserted | |
| 503 // into the tree. | |
| 504 ASSERT(relative->isDescendantOf(this)); | |
| 505 m_spannerMap.remove(toRenderMultiColumnSpannerPlaceholder(relative)->spa nner()); | |
| 506 return; | |
| 507 } | |
| 508 if (relative->style()->columnSpan() == ColumnSpanAll) { | |
| 509 if (relative->parent() != parent()) | |
| 510 return; // not a valid spanner. | |
| 511 | |
| 512 // The placeholder may already have been removed, but if it hasn't, do s o now. | |
| 513 if (RenderMultiColumnSpannerPlaceholder* placeholder = m_spannerMap.get( toRenderBox(relative))) { | |
| 514 placeholder->containingBlock()->RenderBlock::removeChild(placeholder ); | |
| 515 m_spannerMap.remove(toRenderBox(relative)); | |
| 516 } | |
| 517 | |
| 518 if (RenderObject* next = relative->nextSibling()) { | |
| 519 if (RenderObject* previous = relative->previousSibling()) { | |
| 520 if (previous->isRenderMultiColumnSet() && next->isRenderMultiCol umnSet()) { | |
| 521 // Merge two sets that no longer will be separated by a span ner. | |
| 522 next->destroy(); | |
| 523 previous->setNeedsLayoutAndFullPaintInvalidation(); | |
| 524 } | |
| 525 } | |
| 526 } | |
| 527 } | |
| 528 // Note that we might end up with empty column sets if all column content is removed. That's no | |
| 529 // big deal though (and locating them would be expensive), and they will be found and re-used if | |
| 530 // content is added again later. | |
|
Julien - ping for review
2014/09/03 23:22:23
FWIW table used to have some layout bugs from this
mstensho (USE GERRIT)
2014/09/04 12:14:02
Funny that you should mention it. This is still an
| |
| 531 } | |
| 532 | |
| 259 void RenderMultiColumnFlowThread::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const | 533 void RenderMultiColumnFlowThread::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const |
| 260 { | 534 { |
| 261 // We simply remain at our intrinsic height. | 535 // We simply remain at our intrinsic height. |
| 262 computedValues.m_extent = logicalHeight; | 536 computedValues.m_extent = logicalHeight; |
| 263 computedValues.m_position = logicalTop; | 537 computedValues.m_position = logicalTop; |
| 264 } | 538 } |
| 265 | 539 |
| 266 void RenderMultiColumnFlowThread::updateLogicalWidth() | 540 void RenderMultiColumnFlowThread::updateLogicalWidth() |
| 267 { | 541 { |
| 268 LayoutUnit columnWidth; | 542 LayoutUnit columnWidth; |
| 269 calculateColumnCountAndWidth(columnWidth, m_columnCount); | 543 calculateColumnCountAndWidth(columnWidth, m_columnCount); |
| 270 setLogicalWidth(columnWidth); | 544 setLogicalWidth(columnWidth); |
| 271 } | 545 } |
| 272 | 546 |
| 273 void RenderMultiColumnFlowThread::layout() | 547 void RenderMultiColumnFlowThread::layout() |
| 274 { | 548 { |
| 549 ASSERT(!m_inLayout); | |
| 550 m_inLayout = true; | |
| 551 m_lastSetWorkedOn = 0; | |
| 552 if (RenderBox* first = firstColumnSetOrSpanner()) { | |
| 553 if (first->isRenderMultiColumnSet()) { | |
| 554 m_lastSetWorkedOn = toRenderMultiColumnSet(first); | |
| 555 m_lastSetWorkedOn->beginFlow(LayoutUnit()); | |
| 556 } | |
| 557 } | |
| 275 RenderFlowThread::layout(); | 558 RenderFlowThread::layout(); |
| 276 if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) | 559 if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) { |
| 560 if (!nextColumnSetOrSpannerSiblingOf(lastSet)) | |
| 561 lastSet->endFlow(logicalHeight()); | |
| 277 lastSet->expandToEncompassFlowThreadContentsIfNeeded(); | 562 lastSet->expandToEncompassFlowThreadContentsIfNeeded(); |
| 563 } | |
| 564 m_inLayout = false; | |
| 565 m_lastSetWorkedOn = 0; | |
| 278 } | 566 } |
| 279 | 567 |
| 280 void RenderMultiColumnFlowThread::setPageBreak(LayoutUnit offset, LayoutUnit spa ceShortage) | 568 void RenderMultiColumnFlowThread::setPageBreak(LayoutUnit offset, LayoutUnit spa ceShortage) |
| 281 { | 569 { |
| 282 // Only positive values are interesting (and allowed) here. Zero space short age may be reported | 570 // Only positive values are interesting (and allowed) here. Zero space short age may be reported |
| 283 // when we're at the top of a column and the element has zero height. Ignore this, and also | 571 // when we're at the top of a column and the element has zero height. Ignore this, and also |
| 284 // ignore any negative values, which may occur when we set an early break in order to honor | 572 // ignore any negative values, which may occur when we set an early break in order to honor |
| 285 // widows in the next column. | 573 // widows in the next column. |
| 286 if (spaceShortage <= 0) | 574 if (spaceShortage <= 0) |
| 287 return; | 575 return; |
| 288 | 576 |
| 289 if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset)) | 577 if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset)) |
| 290 multicolSet->recordSpaceShortage(spaceShortage); | 578 multicolSet->recordSpaceShortage(spaceShortage); |
| 291 } | 579 } |
| 292 | 580 |
| 293 void RenderMultiColumnFlowThread::updateMinimumPageHeight(LayoutUnit offset, Lay outUnit minHeight) | 581 void RenderMultiColumnFlowThread::updateMinimumPageHeight(LayoutUnit offset, Lay outUnit minHeight) |
| 294 { | 582 { |
| 295 if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset)) | 583 if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset)) |
| 296 multicolSet->updateMinimumColumnHeight(minHeight); | 584 multicolSet->updateMinimumColumnHeight(minHeight); |
| 297 } | 585 } |
| 298 | 586 |
| 299 RenderMultiColumnSet* RenderMultiColumnFlowThread::columnSetAtBlockOffset(Layout Unit /*offset*/) const | 587 RenderMultiColumnSet* RenderMultiColumnFlowThread::columnSetAtBlockOffset(Layout Unit offset) const |
| 300 { | 588 { |
| 301 // For now there's only one column set, so this is easy: | 589 if (m_inLayout) { |
| 302 return firstMultiColumnSet(); | 590 // Layout in progress. We are calculating the set heights as we speak, s o the column set range |
| 591 // information is not up-to-date. | |
| 592 | |
| 593 RenderMultiColumnSet* columnSet = m_lastSetWorkedOn ? m_lastSetWorkedOn : firstMultiColumnSet(); | |
| 594 if (!columnSet) { | |
| 595 // If there's no set, bail. This multicol is empty or only consists of spanners. There | |
| 596 // are no regions. | |
| 597 return 0; | |
| 598 } | |
| 599 // The last set worked on is a good guess. But if we're not within the b ounds, search for the | |
| 600 // right one. | |
| 601 if (offset < columnSet->logicalTopInFlowThread()) { | |
| 602 // In some cases we need to search backwards for a column set we've advanced past. This | |
| 603 // happens when a block crosses a column set boundary, and someone w ants to examine or | |
| 604 // adjust its top after or during layout. FIXME: If its top acually gets adjusted | |
| 605 // (e.g. because of an incorrect initial top position estimate), thi s may be problematic | |
| 606 // for column balancing, but returning the correct set here is at le ast better than | |
| 607 // nothing. | |
| 608 do { | |
| 609 if (RenderMultiColumnSet* prev = columnSet->previousSiblingMulti ColumnSet()) | |
| 610 columnSet = prev; | |
| 611 else | |
| 612 break; | |
| 613 } while (offset < columnSet->logicalTopInFlowThread()); | |
| 614 } else { | |
| 615 // We currently don't support searching forwards for a set, and ther e should be no need | |
| 616 // for it, either. | |
| 617 ASSERT(offset < columnSet->logicalBottomInFlowThread()); | |
| 618 } | |
| 619 return columnSet; | |
| 620 } | |
| 621 | |
| 622 ASSERT(!m_regionsInvalidated); | |
| 623 if (offset <= 0) | |
| 624 return m_multiColumnSetList.isEmpty() ? 0 : m_multiColumnSetList.first() ; | |
| 625 | |
| 626 MultiColumnSetSearchAdapter adapter(offset); | |
| 627 m_multiColumnSetIntervalTree.allOverlapsWithAdapter<MultiColumnSetSearchAdap ter>(adapter); | |
| 628 | |
| 629 // If no set was found, the offset is in the flow thread overflow. | |
| 630 if (!adapter.result() && !m_multiColumnSetList.isEmpty()) | |
| 631 return m_multiColumnSetList.last(); | |
| 632 return adapter.result(); | |
| 303 } | 633 } |
| 304 | 634 |
| 305 bool RenderMultiColumnFlowThread::addForcedRegionBreak(LayoutUnit offset, Render Object* /*breakChild*/, bool /*isBefore*/, LayoutUnit* offsetBreakAdjustment) | 635 bool RenderMultiColumnFlowThread::addForcedRegionBreak(LayoutUnit offset, Render Object* breakChild, bool /*isBefore*/, LayoutUnit* offsetBreakAdjustment) |
| 306 { | 636 { |
| 307 if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset)) { | 637 if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset)) { |
| 308 multicolSet->addContentRun(offset); | 638 // Spanner placeholders force a break (to make sure that the unused part of the last column |
| 639 // is empty), but this break should not affect column balancing. | |
| 640 if (!breakChild->isRenderMultiColumnSpannerPlaceholder()) | |
| 641 multicolSet->addContentRun(offset); | |
| 309 if (offsetBreakAdjustment) | 642 if (offsetBreakAdjustment) |
| 310 *offsetBreakAdjustment = pageLogicalHeightForOffset(offset) ? pageRe mainingLogicalHeightForOffset(offset, IncludePageBoundary) : LayoutUnit(); | 643 *offsetBreakAdjustment = pageLogicalHeightForOffset(offset) ? pageRe mainingLogicalHeightForOffset(offset, IncludePageBoundary) : LayoutUnit(); |
| 311 return true; | 644 return true; |
| 312 } | 645 } |
| 313 return false; | 646 return false; |
| 314 } | 647 } |
| 315 | 648 |
| 316 bool RenderMultiColumnFlowThread::isPageLogicalHeightKnown() const | 649 bool RenderMultiColumnFlowThread::isPageLogicalHeightKnown() const |
| 317 { | 650 { |
| 318 if (RenderMultiColumnSet* columnSet = lastMultiColumnSet()) | 651 if (RenderMultiColumnSet* columnSet = lastMultiColumnSet()) |
| 319 return columnSet->pageLogicalHeight(); | 652 return columnSet->pageLogicalHeight(); |
| 320 return false; | 653 return false; |
| 321 } | 654 } |
| 322 | 655 |
| 323 } | 656 } |
| OLD | NEW |