| 1 | /* |
| 2 | * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| 3 | * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| 4 | * (C) 2007 David Smith (catfish.man@gmail.com) |
| 5 | * Copyright (C) 2003-2015, 2017 Apple Inc. All rights reserved. |
| 6 | * Copyright (C) Research In Motion Limited 2010. All rights reserved. |
| 7 | * |
| 8 | * This library is free software; you can redistribute it and/or |
| 9 | * modify it under the terms of the GNU Library General Public |
| 10 | * License as published by the Free Software Foundation; either |
| 11 | * version 2 of the License, or (at your option) any later version. |
| 12 | * |
| 13 | * This library is distributed in the hope that it will be useful, |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 16 | * Library General Public License for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU Library General Public License |
| 19 | * along with this library; see the file COPYING.LIB. If not, write to |
| 20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 21 | * Boston, MA 02110-1301, USA. |
| 22 | */ |
| 23 | |
| 24 | #include "config.h" |
| 25 | #include "RenderTreeBuilderMultiColumn.h" |
| 26 | |
| 27 | #include "RenderBlockFlow.h" |
| 28 | #include "RenderChildIterator.h" |
| 29 | #include "RenderMultiColumnFlow.h" |
| 30 | #include "RenderMultiColumnSet.h" |
| 31 | #include "RenderMultiColumnSpannerPlaceholder.h" |
| 32 | #include "RenderTreeBuilder.h" |
| 33 | #include "RenderTreeBuilderBlock.h" |
| 34 | |
| 35 | namespace WebCore { |
| 36 | |
| 37 | static RenderMultiColumnSet* findSetRendering(const RenderMultiColumnFlow& fragmentedFlow, const RenderObject& renderer) |
| 38 | { |
| 39 | // Find the set inside which the specified renderer would be rendered. |
| 40 | for (auto* multicolSet = fragmentedFlow.firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) { |
| 41 | if (multicolSet->containsRendererInFragmentedFlow(renderer)) |
| 42 | return multicolSet; |
| 43 | } |
| 44 | return nullptr; |
| 45 | } |
| 46 | |
| 47 | static RenderObject* spannerPlacehoderCandidate(const RenderObject& renderer, const RenderMultiColumnFlow& stayWithin) |
| 48 | { |
| 49 | // Spanner candidate is a next sibling/ancestor's next child within the flow thread and |
| 50 | // it is in the same inflow/out-of-flow layout context. |
| 51 | if (renderer.isOutOfFlowPositioned()) |
| 52 | return nullptr; |
| 53 | |
| 54 | ASSERT(renderer.isDescendantOf(&stayWithin)); |
| 55 | auto* current = &renderer; |
| 56 | while (true) { |
| 57 | // Skip to the first in-flow sibling. |
| 58 | auto* nextSibling = current->nextSibling(); |
| 59 | while (nextSibling && nextSibling->isOutOfFlowPositioned()) |
| 60 | nextSibling = nextSibling->nextSibling(); |
| 61 | if (nextSibling) |
| 62 | return nextSibling; |
| 63 | // No sibling candidate, jump to the parent and check its siblings. |
| 64 | current = current->parent(); |
| 65 | if (!current || current == &stayWithin || current->isOutOfFlowPositioned()) |
| 66 | return nullptr; |
| 67 | } |
| 68 | return nullptr; |
| 69 | } |
| 70 | |
| 71 | static bool isValidColumnSpanner(const RenderMultiColumnFlow& fragmentedFlow, const RenderObject& descendant) |
| 72 | { |
| 73 | // We assume that we're inside the flow thread. This function is not to be called otherwise. |
| 74 | ASSERT(descendant.isDescendantOf(&fragmentedFlow)); |
| 75 | // First make sure that the renderer itself has the right properties for becoming a spanner. |
| 76 | if (!is<RenderBox>(descendant)) |
| 77 | return false; |
| 78 | |
| 79 | auto& descendantBox = downcast<RenderBox>(descendant); |
| 80 | if (descendantBox.isFloatingOrOutOfFlowPositioned()) |
| 81 | return false; |
| 82 | |
| 83 | if (descendantBox.style().columnSpan() != ColumnSpan::All) |
| 84 | return false; |
| 85 | |
| 86 | auto* parent = descendantBox.parent(); |
| 87 | if (!is<RenderBlockFlow>(*parent) || parent->childrenInline()) { |
| 88 | // Needs to be block-level. |
| 89 | return false; |
| 90 | } |
| 91 | |
| 92 | // We need to have the flow thread as the containing block. A spanner cannot break out of the flow thread. |
| 93 | auto* enclosingFragmentedFlow = descendantBox.enclosingFragmentedFlow(); |
| 94 | if (enclosingFragmentedFlow != &fragmentedFlow) |
| 95 | return false; |
| 96 | |
| 97 | // This looks like a spanner, but if we're inside something unbreakable, it's not to be treated as one. |
| 98 | for (auto* ancestor = descendantBox.containingBlock(); ancestor; ancestor = ancestor->containingBlock()) { |
| 99 | if (is<RenderView>(*ancestor)) |
| 100 | return false; |
| 101 | if (is<RenderFragmentedFlow>(*ancestor)) { |
| 102 | // Don't allow any intervening non-multicol fragmentation contexts. The spec doesn't say |
| 103 | // anything about disallowing this, but it's just going to be too complicated to |
| 104 | // implement (not to mention specify behavior). |
| 105 | return ancestor == &fragmentedFlow; |
| 106 | } |
| 107 | // This ancestor (descendent of the fragmentedFlow) will create columns later. The spanner belongs to it. |
| 108 | if (is<RenderBlockFlow>(*ancestor) && downcast<RenderBlockFlow>(*ancestor).willCreateColumns()) |
| 109 | return false; |
| 110 | ASSERT(ancestor->style().columnSpan() != ColumnSpan::All || !isValidColumnSpanner(fragmentedFlow, *ancestor)); |
| 111 | if (ancestor->isUnsplittableForPagination()) |
| 112 | return false; |
| 113 | } |
| 114 | ASSERT_NOT_REACHED(); |
| 115 | return false; |
| 116 | } |
| 117 | |
| 118 | RenderTreeBuilder::MultiColumn::MultiColumn(RenderTreeBuilder& builder) |
| 119 | : m_builder(builder) |
| 120 | { |
| 121 | } |
| 122 | |
| 123 | void RenderTreeBuilder::MultiColumn::updateAfterDescendants(RenderBlockFlow& flow) |
| 124 | { |
| 125 | bool needsFragmentedFlow = flow.requiresColumns(flow.style().columnCount()); |
| 126 | bool hasFragmentedFlow = flow.multiColumnFlow(); |
| 127 | |
| 128 | if (!hasFragmentedFlow && needsFragmentedFlow) { |
| 129 | createFragmentedFlow(flow); |
| 130 | return; |
| 131 | } |
| 132 | if (hasFragmentedFlow && !needsFragmentedFlow) { |
| 133 | destroyFragmentedFlow(flow); |
| 134 | return; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | void RenderTreeBuilder::MultiColumn::createFragmentedFlow(RenderBlockFlow& flow) |
| 139 | { |
| 140 | flow.setChildrenInline(false); // Do this to avoid wrapping inline children that are just going to move into the flow thread. |
| 141 | flow.deleteLines(); |
| 142 | // If this soon-to-be multicolumn flow is already part of a multicolumn context, we need to move back the descendant spanners |
| 143 | // to their original position before moving subtrees around. |
| 144 | auto* enclosingflow = flow.enclosingFragmentedFlow(); |
| 145 | if (is<RenderMultiColumnFlow>(enclosingflow)) { |
| 146 | auto& spanners = downcast<RenderMultiColumnFlow>(enclosingflow)->spannerMap(); |
| 147 | Vector<RenderMultiColumnSpannerPlaceholder*> placeholdersToDelete; |
| 148 | for (auto& spannerAndPlaceholder : spanners) { |
| 149 | auto& placeholder = *spannerAndPlaceholder.value; |
| 150 | if (!placeholder.isDescendantOf(&flow)) |
| 151 | continue; |
| 152 | placeholdersToDelete.append(&placeholder); |
| 153 | } |
| 154 | for (auto* placeholder : placeholdersToDelete) { |
| 155 | auto* spanner = placeholder->spanner(); |
| 156 | if (!spanner) { |
| 157 | ASSERT_NOT_REACHED(); |
| 158 | continue; |
| 159 | } |
| 160 | // Move the spanner back to its original position. |
| 161 | auto& spannerOriginalParent = *placeholder->parent(); |
| 162 | // Detaching the spanner takes care of removing the placeholder (and merges the RenderMultiColumnSets). |
| 163 | auto spannerToReInsert = m_builder.detach(*spanner->parent(), *spanner); |
| 164 | m_builder.attach(spannerOriginalParent, WTFMove(spannerToReInsert)); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | auto newFragmentedFlow = WebCore::createRenderer<RenderMultiColumnFlow>(flow.document(), RenderStyle::createAnonymousStyleWithDisplay(flow.style(), DisplayType::Block)); |
| 169 | newFragmentedFlow->initializeStyle(); |
| 170 | auto& fragmentedFlow = *newFragmentedFlow; |
| 171 | m_builder.blockBuilder().attach(flow, WTFMove(newFragmentedFlow), nullptr); |
| 172 | |
| 173 | // Reparent children preceding the fragmented flow into the fragmented flow. |
| 174 | m_builder.moveChildren(flow, fragmentedFlow, flow.firstChild(), &fragmentedFlow, RenderTreeBuilder::NormalizeAfterInsertion::Yes); |
| 175 | if (flow.isFieldset()) { |
| 176 | // Keep legends out of the flow thread. |
| 177 | for (auto& box : childrenOfType<RenderBox>(fragmentedFlow)) { |
| 178 | if (box.isLegend()) |
| 179 | m_builder.move(fragmentedFlow, flow, box, RenderTreeBuilder::NormalizeAfterInsertion::Yes); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | flow.setMultiColumnFlow(fragmentedFlow); |
| 184 | } |
| 185 | |
| 186 | void RenderTreeBuilder::MultiColumn::destroyFragmentedFlow(RenderBlockFlow& flow) |
| 187 | { |
| 188 | auto& multiColumnFlow = *flow.multiColumnFlow(); |
| 189 | multiColumnFlow.deleteLines(); |
| 190 | |
| 191 | // Move spanners back to their original DOM position in the tree, and destroy the placeholders. |
| 192 | auto& spanners = multiColumnFlow.spannerMap(); |
| 193 | Vector<RenderMultiColumnSpannerPlaceholder*> placeholdersToDelete; |
| 194 | for (auto& spannerAndPlaceholder : spanners) |
| 195 | placeholdersToDelete.append(spannerAndPlaceholder.value.get()); |
| 196 | Vector<std::pair<RenderElement*, RenderPtr<RenderObject>>> parentAndSpannerList; |
| 197 | for (auto* placeholder : placeholdersToDelete) { |
| 198 | auto* spannerOriginalParent = placeholder->parent(); |
| 199 | if (spannerOriginalParent == &multiColumnFlow) |
| 200 | spannerOriginalParent = &flow; |
| 201 | // Detaching the spanner takes care of removing the placeholder (and merges the RenderMultiColumnSets). |
| 202 | auto* spanner = placeholder->spanner(); |
| 203 | parentAndSpannerList.append(std::make_pair(spannerOriginalParent, m_builder.detach(*spanner->parent(), *spanner))); |
| 204 | } |
| 205 | while (auto* columnSet = multiColumnFlow.firstMultiColumnSet()) |
| 206 | m_builder.destroy(*columnSet); |
| 207 | |
| 208 | flow.clearMultiColumnFlow(); |
| 209 | m_builder.moveAllChildren(multiColumnFlow, flow, RenderTreeBuilder::NormalizeAfterInsertion::Yes); |
| 210 | m_builder.destroy(multiColumnFlow); |
| 211 | for (auto& parentAndSpanner : parentAndSpannerList) |
| 212 | m_builder.attach(*parentAndSpanner.first, WTFMove(parentAndSpanner.second)); |
| 213 | } |
| 214 | |
| 215 | |
| 216 | RenderObject* RenderTreeBuilder::MultiColumn::resolveMovedChild(RenderFragmentedFlow& enclosingFragmentedFlow, RenderObject* beforeChild) |
| 217 | { |
| 218 | if (!beforeChild) |
| 219 | return nullptr; |
| 220 | |
| 221 | if (!is<RenderBox>(*beforeChild)) |
| 222 | return beforeChild; |
| 223 | |
| 224 | if (!is<RenderMultiColumnFlow>(enclosingFragmentedFlow)) |
| 225 | return beforeChild; |
| 226 | |
| 227 | // We only need to resolve for column spanners. |
| 228 | if (beforeChild->style().columnSpan() != ColumnSpan::All) |
| 229 | return beforeChild; |
| 230 | |
| 231 | // The renderer for the actual DOM node that establishes a spanner is moved from its original |
| 232 | // location in the render tree to becoming a sibling of the column sets. In other words, it's |
| 233 | // moved out from the flow thread (and becomes a sibling of it). When we for instance want to |
| 234 | // create and insert a renderer for the sibling node immediately preceding the spanner, we need |
| 235 | // to map that spanner renderer to the spanner's placeholder, which is where the new inserted |
| 236 | // renderer belongs. |
| 237 | if (auto* placeholder = downcast<RenderMultiColumnFlow>(enclosingFragmentedFlow).findColumnSpannerPlaceholder(downcast<RenderBox>(beforeChild))) |
| 238 | return placeholder; |
| 239 | |
| 240 | // This is an invalid spanner, or its placeholder hasn't been created yet. This happens when |
| 241 | // moving an entire subtree into the flow thread, when we are processing the insertion of this |
| 242 | // spanner's preceding sibling, and we obviously haven't got as far as processing this spanner |
| 243 | // yet. |
| 244 | return beforeChild; |
| 245 | } |
| 246 | |
| 247 | static bool gShiftingSpanner = false; |
| 248 | |
| 249 | void RenderTreeBuilder::MultiColumn::multiColumnDescendantInserted(RenderMultiColumnFlow& flow, RenderObject& newDescendant) |
| 250 | { |
| 251 | if (gShiftingSpanner || newDescendant.isInFlowRenderFragmentedFlow()) |
| 252 | return; |
| 253 | |
| 254 | auto* subtreeRoot = &newDescendant; |
| 255 | auto* descendant = subtreeRoot; |
| 256 | while (descendant) { |
| 257 | // Skip nested multicolumn flows. |
| 258 | if (is<RenderMultiColumnFlow>(*descendant)) { |
| 259 | descendant = descendant->nextSibling(); |
| 260 | continue; |
| 261 | } |
| 262 | if (is<RenderMultiColumnSpannerPlaceholder>(*descendant)) { |
| 263 | // A spanner's placeholder has been inserted. The actual spanner renderer is moved from |
| 264 | // where it would otherwise occur (if it weren't a spanner) to becoming a sibling of the |
| 265 | // column sets. |
| 266 | RenderMultiColumnSpannerPlaceholder& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*descendant); |
| 267 | ASSERT(!flow.spannerMap().get(placeholder.spanner())); |
| 268 | flow.spannerMap().add(placeholder.spanner(), makeWeakPtr(downcast<RenderMultiColumnSpannerPlaceholder>(descendant))); |
| 269 | ASSERT(!placeholder.firstChild()); // There should be no children here, but if there are, we ought to skip them. |
| 270 | } else |
| 271 | descendant = processPossibleSpannerDescendant(flow, subtreeRoot, *descendant); |
| 272 | if (descendant) |
| 273 | descendant = descendant->nextInPreOrder(subtreeRoot); |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | RenderObject* RenderTreeBuilder::MultiColumn::processPossibleSpannerDescendant(RenderMultiColumnFlow& flow, RenderObject*& subtreeRoot, RenderObject& descendant) |
| 278 | { |
| 279 | RenderBlockFlow* multicolContainer = flow.multiColumnBlockFlow(); |
| 280 | RenderObject* nextRendererInFragmentedFlow = spannerPlacehoderCandidate(descendant, flow); |
| 281 | RenderObject* insertBeforeMulticolChild = nullptr; |
| 282 | RenderObject* nextDescendant = &descendant; |
| 283 | |
| 284 | if (isValidColumnSpanner(flow, descendant)) { |
| 285 | // This is a spanner (column-span:all). Such renderers are moved from where they would |
| 286 | // otherwise occur in the render tree to becoming a direct child of the multicol container, |
| 287 | // so that they live among the column sets. This simplifies the layout implementation, and |
| 288 | // basically just relies on regular block layout done by the RenderBlockFlow that |
| 289 | // establishes the multicol container. |
| 290 | RenderBlockFlow* container = downcast<RenderBlockFlow>(descendant.parent()); |
| 291 | RenderMultiColumnSet* setToSplit = nullptr; |
| 292 | if (nextRendererInFragmentedFlow) { |
| 293 | setToSplit = findSetRendering(flow, descendant); |
| 294 | if (setToSplit) { |
| 295 | setToSplit->setNeedsLayout(); |
| 296 | insertBeforeMulticolChild = setToSplit->nextSibling(); |
| 297 | } |
| 298 | } |
| 299 | // Moving a spanner's renderer so that it becomes a sibling of the column sets requires us |
| 300 | // to insert an anonymous placeholder in the tree where the spanner's renderer otherwise |
| 301 | // would have been. This is needed for a two reasons: We need a way of separating inline |
| 302 | // content before and after the spanner, so that it becomes separate line boxes. Secondly, |
| 303 | // this placeholder serves as a break point for column sets, so that, when encountered, we |
| 304 | // end flowing one column set and move to the next one. |
| 305 | auto newPlaceholder = RenderMultiColumnSpannerPlaceholder::createAnonymous(flow, downcast<RenderBox>(descendant), container->style()); |
| 306 | auto& placeholder = *newPlaceholder; |
| 307 | m_builder.attach(*container, WTFMove(newPlaceholder), descendant.nextSibling()); |
| 308 | auto takenDescendant = m_builder.detach(*container, descendant); |
| 309 | |
| 310 | // This is a guard to stop an ancestor flow thread from processing the spanner. |
| 311 | gShiftingSpanner = true; |
| 312 | m_builder.blockBuilder().attach(*multicolContainer, WTFMove(takenDescendant), insertBeforeMulticolChild); |
| 313 | gShiftingSpanner = false; |
| 314 | |
| 315 | // The spanner has now been moved out from the flow thread, but we don't want to |
| 316 | // examine its children anyway. They are all part of the spanner and shouldn't trigger |
| 317 | // creation of column sets or anything like that. Continue at its original position in |
| 318 | // the tree, i.e. where the placeholder was just put. |
| 319 | if (subtreeRoot == &descendant) |
| 320 | subtreeRoot = &placeholder; |
| 321 | nextDescendant = &placeholder; |
| 322 | } else { |
| 323 | // This is regular multicol content, i.e. not part of a spanner. |
| 324 | if (is<RenderMultiColumnSpannerPlaceholder>(nextRendererInFragmentedFlow)) { |
| 325 | // Inserted right before a spanner. Is there a set for us there? |
| 326 | RenderMultiColumnSpannerPlaceholder& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*nextRendererInFragmentedFlow); |
| 327 | if (RenderObject* previous = placeholder.spanner()->previousSibling()) { |
| 328 | if (is<RenderMultiColumnSet>(*previous)) |
| 329 | return nextDescendant; // There's already a set there. Nothing to do. |
| 330 | } |
| 331 | insertBeforeMulticolChild = placeholder.spanner(); |
| 332 | } else if (RenderMultiColumnSet* lastSet = flow.lastMultiColumnSet()) { |
| 333 | // This child is not an immediate predecessor of a spanner, which means that if this |
| 334 | // child precedes a spanner at all, there has to be a column set created for us there |
| 335 | // already. If it doesn't precede any spanner at all, on the other hand, we need a |
| 336 | // column set at the end of the multicol container. We don't really check here if the |
| 337 | // child inserted precedes any spanner or not (as that's an expensive operation). Just |
| 338 | // make sure we have a column set at the end. It's no big deal if it remains unused. |
| 339 | if (!lastSet->nextSibling()) |
| 340 | return nextDescendant; |
| 341 | } |
| 342 | } |
| 343 | // Need to create a new column set when there's no set already created. We also always insert |
| 344 | // another column set after a spanner. Even if it turns out that there are no renderers |
| 345 | // following the spanner, there may be bottom margins there, which take up space. |
| 346 | auto newSet = createRenderer<RenderMultiColumnSet>(flow, RenderStyle::createAnonymousStyleWithDisplay(multicolContainer->style(), DisplayType::Block)); |
| 347 | newSet->initializeStyle(); |
| 348 | auto& set = *newSet; |
| 349 | m_builder.blockBuilder().attach(*multicolContainer, WTFMove(newSet), insertBeforeMulticolChild); |
| 350 | flow.invalidateFragments(); |
| 351 | |
| 352 | // We cannot handle immediate column set siblings at the moment (and there's no need for |
| 353 | // it, either). There has to be at least one spanner separating them. |
| 354 | ASSERT_UNUSED(set, !RenderMultiColumnFlow::previousColumnSetOrSpannerSiblingOf(&set) |
| 355 | || !RenderMultiColumnFlow::previousColumnSetOrSpannerSiblingOf(&set)->isRenderMultiColumnSet()); |
| 356 | ASSERT(!RenderMultiColumnFlow::nextColumnSetOrSpannerSiblingOf(&set) |
| 357 | || !RenderMultiColumnFlow::nextColumnSetOrSpannerSiblingOf(&set)->isRenderMultiColumnSet()); |
| 358 | |
| 359 | return nextDescendant; |
| 360 | } |
| 361 | |
| 362 | void RenderTreeBuilder::MultiColumn::handleSpannerRemoval(RenderMultiColumnFlow& flow, RenderObject& spanner) |
| 363 | { |
| 364 | // The placeholder may already have been removed, but if it hasn't, do so now. |
| 365 | if (auto placeholder = flow.spannerMap().take(&downcast<RenderBox>(spanner))) |
| 366 | m_builder.destroy(*placeholder); |
| 367 | |
| 368 | if (auto* next = spanner.nextSibling()) { |
| 369 | if (auto* previous = spanner.previousSibling()) { |
| 370 | if (previous->isRenderMultiColumnSet() && next->isRenderMultiColumnSet()) { |
| 371 | // Merge two sets that no longer will be separated by a spanner. |
| 372 | m_builder.destroy(*next); |
| 373 | previous->setNeedsLayout(); |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | } |
| 378 | |
| 379 | void RenderTreeBuilder::MultiColumn::multiColumnRelativeWillBeRemoved(RenderMultiColumnFlow& flow, RenderObject& relative) |
| 380 | { |
| 381 | flow.invalidateFragments(); |
| 382 | if (is<RenderMultiColumnSpannerPlaceholder>(relative)) { |
| 383 | // Remove the map entry for this spanner, but leave the actual spanner renderer alone. Also |
| 384 | // keep the reference to the spanner, since the placeholder may be about to be re-inserted |
| 385 | // in the tree. |
| 386 | ASSERT(relative.isDescendantOf(&flow)); |
| 387 | flow.spannerMap().remove(downcast<RenderMultiColumnSpannerPlaceholder>(relative).spanner()); |
| 388 | return; |
| 389 | } |
| 390 | if (relative.style().columnSpan() == ColumnSpan::All) { |
| 391 | if (relative.parent() != flow.parent()) |
| 392 | return; // not a valid spanner. |
| 393 | |
| 394 | handleSpannerRemoval(flow, relative); |
| 395 | } |
| 396 | // Note that we might end up with empty column sets if all column content is removed. That's no |
| 397 | // big deal though (and locating them would be expensive), and they will be found and re-used if |
| 398 | // content is added again later. |
| 399 | } |
| 400 | |
| 401 | RenderObject* RenderTreeBuilder::MultiColumn::adjustBeforeChildForMultiColumnSpannerIfNeeded(RenderObject& beforeChild) |
| 402 | { |
| 403 | if (!is<RenderBox>(beforeChild)) |
| 404 | return &beforeChild; |
| 405 | |
| 406 | auto* nextSibling = beforeChild.nextSibling(); |
| 407 | if (!nextSibling) |
| 408 | return &beforeChild; |
| 409 | |
| 410 | if (!is<RenderMultiColumnSet>(*nextSibling)) |
| 411 | return &beforeChild; |
| 412 | |
| 413 | auto* multiColumnFlow = downcast<RenderMultiColumnSet>(*nextSibling).multiColumnFlow(); |
| 414 | if (!multiColumnFlow) |
| 415 | return &beforeChild; |
| 416 | |
| 417 | return multiColumnFlow->findColumnSpannerPlaceholder(downcast<RenderBox>(&beforeChild)); |
| 418 | } |
| 419 | |
| 420 | } |
| 421 | |