| 1 | /* |
| 2 | * Copyright (C) 2017 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 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. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| 14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| 15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| 17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| 23 | * THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "RenderTreeBuilderBlock.h" |
| 28 | |
| 29 | #include "RenderButton.h" |
| 30 | #include "RenderChildIterator.h" |
| 31 | #include "RenderFullScreen.h" |
| 32 | #include "RenderMultiColumnFlow.h" |
| 33 | #include "RenderRuby.h" |
| 34 | #include "RenderRubyRun.h" |
| 35 | #include "RenderTextControl.h" |
| 36 | #include "RenderTreeBuilderMultiColumn.h" |
| 37 | |
| 38 | namespace WebCore { |
| 39 | |
| 40 | static void moveAllChildrenToInternal(RenderBoxModelObject& from, RenderElement& newParent) |
| 41 | { |
| 42 | while (from.firstChild()) |
| 43 | newParent.attachRendererInternal(from.detachRendererInternal(*from.firstChild()), &from); |
| 44 | } |
| 45 | |
| 46 | static bool canDropAnonymousBlock(const RenderBlock& anonymousBlock) |
| 47 | { |
| 48 | if (anonymousBlock.beingDestroyed() || anonymousBlock.continuation()) |
| 49 | return false; |
| 50 | if (anonymousBlock.isRubyRun() || anonymousBlock.isRubyBase()) |
| 51 | return false; |
| 52 | return true; |
| 53 | } |
| 54 | |
| 55 | static bool canMergeContiguousAnonymousBlocks(RenderObject& oldChild, RenderObject* previous, RenderObject* next) |
| 56 | { |
| 57 | ASSERT(!oldChild.renderTreeBeingDestroyed()); |
| 58 | |
| 59 | if (oldChild.isInline()) |
| 60 | return false; |
| 61 | |
| 62 | if (is<RenderBoxModelObject>(oldChild) && downcast<RenderBoxModelObject>(oldChild).continuation()) |
| 63 | return false; |
| 64 | |
| 65 | if (previous) { |
| 66 | if (!previous->isAnonymousBlock()) |
| 67 | return false; |
| 68 | RenderBlock& previousAnonymousBlock = downcast<RenderBlock>(*previous); |
| 69 | if (!canDropAnonymousBlock(previousAnonymousBlock)) |
| 70 | return false; |
| 71 | } |
| 72 | if (next) { |
| 73 | if (!next->isAnonymousBlock()) |
| 74 | return false; |
| 75 | RenderBlock& nextAnonymousBlock = downcast<RenderBlock>(*next); |
| 76 | if (!canDropAnonymousBlock(nextAnonymousBlock)) |
| 77 | return false; |
| 78 | } |
| 79 | return true; |
| 80 | } |
| 81 | |
| 82 | static RenderBlock* continuationBefore(RenderBlock& parent, RenderObject* beforeChild) |
| 83 | { |
| 84 | if (beforeChild && beforeChild->parent() == &parent) |
| 85 | return &parent; |
| 86 | |
| 87 | RenderBlock* nextToLast = &parent; |
| 88 | RenderBlock* last = &parent; |
| 89 | for (auto* current = downcast<RenderBlock>(parent.continuation()); current; current = downcast<RenderBlock>(current->continuation())) { |
| 90 | if (beforeChild && beforeChild->parent() == current) { |
| 91 | if (current->firstChild() == beforeChild) |
| 92 | return last; |
| 93 | return current; |
| 94 | } |
| 95 | |
| 96 | nextToLast = last; |
| 97 | last = current; |
| 98 | } |
| 99 | |
| 100 | if (!beforeChild && !last->firstChild()) |
| 101 | return nextToLast; |
| 102 | return last; |
| 103 | } |
| 104 | |
| 105 | RenderTreeBuilder::Block::Block(RenderTreeBuilder& builder) |
| 106 | : m_builder(builder) |
| 107 | { |
| 108 | } |
| 109 | |
| 110 | void RenderTreeBuilder::Block::attach(RenderBlock& parent, RenderPtr<RenderObject> child, RenderObject* beforeChild) |
| 111 | { |
| 112 | if (parent.continuation() && !parent.isAnonymousBlock()) |
| 113 | insertChildToContinuation(parent, WTFMove(child), beforeChild); |
| 114 | else |
| 115 | attachIgnoringContinuation(parent, WTFMove(child), beforeChild); |
| 116 | } |
| 117 | |
| 118 | void RenderTreeBuilder::Block::insertChildToContinuation(RenderBlock& parent, RenderPtr<RenderObject> child, RenderObject* beforeChild) |
| 119 | { |
| 120 | RenderBlock* flow = continuationBefore(parent, beforeChild); |
| 121 | ASSERT(!beforeChild || is<RenderBlock>(*beforeChild->parent())); |
| 122 | RenderBoxModelObject* beforeChildParent = nullptr; |
| 123 | if (beforeChild) |
| 124 | beforeChildParent = downcast<RenderBoxModelObject>(beforeChild->parent()); |
| 125 | else { |
| 126 | RenderBoxModelObject* continuation = flow->continuation(); |
| 127 | if (continuation) |
| 128 | beforeChildParent = continuation; |
| 129 | else |
| 130 | beforeChildParent = flow; |
| 131 | } |
| 132 | |
| 133 | if (child->isFloatingOrOutOfFlowPositioned()) { |
| 134 | m_builder.attachIgnoringContinuation(*beforeChildParent, WTFMove(child), beforeChild); |
| 135 | return; |
| 136 | } |
| 137 | |
| 138 | bool childIsNormal = child->isInline() || child->style().columnSpan() == ColumnSpan::None; |
| 139 | bool bcpIsNormal = beforeChildParent->isInline() || beforeChildParent->style().columnSpan() == ColumnSpan::None; |
| 140 | bool flowIsNormal = flow->isInline() || flow->style().columnSpan() == ColumnSpan::None; |
| 141 | |
| 142 | if (flow == beforeChildParent) { |
| 143 | m_builder.attachIgnoringContinuation(*flow, WTFMove(child), beforeChild); |
| 144 | return; |
| 145 | } |
| 146 | |
| 147 | // The goal here is to match up if we can, so that we can coalesce and create the |
| 148 | // minimal # of continuations needed for the inline. |
| 149 | if (childIsNormal == bcpIsNormal) { |
| 150 | m_builder.attachIgnoringContinuation(*beforeChildParent, WTFMove(child), beforeChild); |
| 151 | return; |
| 152 | } |
| 153 | if (flowIsNormal == childIsNormal) { |
| 154 | m_builder.attachIgnoringContinuation(*flow, WTFMove(child)); // Just treat like an append. |
| 155 | return; |
| 156 | } |
| 157 | m_builder.attachIgnoringContinuation(*beforeChildParent, WTFMove(child), beforeChild); |
| 158 | } |
| 159 | |
| 160 | void RenderTreeBuilder::Block::attachIgnoringContinuation(RenderBlock& parent, RenderPtr<RenderObject> child, RenderObject* beforeChild) |
| 161 | { |
| 162 | if (beforeChild && beforeChild->parent() != &parent) { |
| 163 | RenderElement* beforeChildContainer = beforeChild->parent(); |
| 164 | while (beforeChildContainer->parent() != &parent) |
| 165 | beforeChildContainer = beforeChildContainer->parent(); |
| 166 | ASSERT(beforeChildContainer); |
| 167 | |
| 168 | if (beforeChildContainer->isAnonymous()) { |
| 169 | RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!beforeChildContainer->isInline()); |
| 170 | |
| 171 | // If the requested beforeChild is not one of our children, then this is because |
| 172 | // there is an anonymous container within this object that contains the beforeChild. |
| 173 | RenderElement* beforeChildAnonymousContainer = beforeChildContainer; |
| 174 | if (beforeChildAnonymousContainer->isAnonymousBlock() |
| 175 | #if ENABLE(FULLSCREEN_API) |
| 176 | // Full screen renderers and full screen placeholders act as anonymous blocks, not tables: |
| 177 | || beforeChildAnonymousContainer->isRenderFullScreen() |
| 178 | || beforeChildAnonymousContainer->isRenderFullScreenPlaceholder() |
| 179 | #endif |
| 180 | ) { |
| 181 | // Insert the child into the anonymous block box instead of here. |
| 182 | if (child->isInline() || beforeChild->parent()->firstChild() != beforeChild) |
| 183 | m_builder.attach(*beforeChild->parent(), WTFMove(child), beforeChild); |
| 184 | else |
| 185 | m_builder.attach(parent, WTFMove(child), beforeChild->parent()); |
| 186 | return; |
| 187 | } |
| 188 | |
| 189 | ASSERT(beforeChildAnonymousContainer->isTable()); |
| 190 | |
| 191 | if (child->isTablePart()) { |
| 192 | // Insert into the anonymous table. |
| 193 | m_builder.attach(*beforeChildAnonymousContainer, WTFMove(child), beforeChild); |
| 194 | return; |
| 195 | } |
| 196 | |
| 197 | beforeChild = m_builder.splitAnonymousBoxesAroundChild(parent, *beforeChild); |
| 198 | |
| 199 | RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(beforeChild->parent() == &parent); |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | bool madeBoxesNonInline = false; |
| 204 | |
| 205 | // A block has to either have all of its children inline, or all of its children as blocks. |
| 206 | // So, if our children are currently inline and a block child has to be inserted, we move all our |
| 207 | // inline children into anonymous block boxes. |
| 208 | if (parent.childrenInline() && !child->isInline() && !child->isFloatingOrOutOfFlowPositioned()) { |
| 209 | // This is a block with inline content. Wrap the inline content in anonymous blocks. |
| 210 | m_builder.makeChildrenNonInline(parent, beforeChild); |
| 211 | madeBoxesNonInline = true; |
| 212 | |
| 213 | if (beforeChild && beforeChild->parent() != &parent) { |
| 214 | beforeChild = beforeChild->parent(); |
| 215 | ASSERT(beforeChild->isAnonymousBlock()); |
| 216 | ASSERT(beforeChild->parent() == &parent); |
| 217 | } |
| 218 | } else if (!parent.childrenInline() && (child->isFloatingOrOutOfFlowPositioned() || child->isInline())) { |
| 219 | // If we're inserting an inline child but all of our children are blocks, then we have to make sure |
| 220 | // it is put into an anomyous block box. We try to use an existing anonymous box if possible, otherwise |
| 221 | // a new one is created and inserted into our list of children in the appropriate position. |
| 222 | RenderObject* afterChild = beforeChild ? beforeChild->previousSibling() : parent.lastChild(); |
| 223 | |
| 224 | if (afterChild && afterChild->isAnonymousBlock()) { |
| 225 | m_builder.attach(downcast<RenderBlock>(*afterChild), WTFMove(child)); |
| 226 | return; |
| 227 | } |
| 228 | |
| 229 | if (child->isInline()) { |
| 230 | // No suitable existing anonymous box - create a new one. |
| 231 | auto newBox = parent.createAnonymousBlock(); |
| 232 | auto& box = *newBox; |
| 233 | m_builder.attachToRenderElement(parent, WTFMove(newBox), beforeChild); |
| 234 | m_builder.attach(box, WTFMove(child)); |
| 235 | return; |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | parent.invalidateLineLayoutPath(); |
| 240 | |
| 241 | m_builder.attachToRenderElement(parent, WTFMove(child), beforeChild); |
| 242 | |
| 243 | if (madeBoxesNonInline && is<RenderBlock>(parent.parent()) && parent.isAnonymousBlock()) |
| 244 | removeLeftoverAnonymousBlock(parent); |
| 245 | // parent object may be dead here |
| 246 | } |
| 247 | |
| 248 | void RenderTreeBuilder::Block::childBecameNonInline(RenderBlock& parent, RenderElement&) |
| 249 | { |
| 250 | m_builder.makeChildrenNonInline(parent); |
| 251 | if (parent.isAnonymousBlock() && is<RenderBlock>(parent.parent())) |
| 252 | removeLeftoverAnonymousBlock(parent); |
| 253 | // parent may be dead here |
| 254 | } |
| 255 | |
| 256 | void RenderTreeBuilder::Block::removeLeftoverAnonymousBlock(RenderBlock& anonymousBlock) |
| 257 | { |
| 258 | ASSERT(anonymousBlock.isAnonymousBlock()); |
| 259 | ASSERT(!anonymousBlock.childrenInline()); |
| 260 | ASSERT(anonymousBlock.parent()); |
| 261 | |
| 262 | if (anonymousBlock.continuation()) |
| 263 | return; |
| 264 | |
| 265 | auto* parent = anonymousBlock.parent(); |
| 266 | if (is<RenderButton>(*parent) || is<RenderTextControl>(*parent) || is<RenderRubyAsBlock>(*parent) || is<RenderRubyRun>(*parent)) |
| 267 | return; |
| 268 | |
| 269 | // FIXME: This should really just be a moveAllChilrenTo (see webkit.org/b/182495) |
| 270 | moveAllChildrenToInternal(anonymousBlock, *parent); |
| 271 | auto toBeDestroyed = m_builder.detachFromRenderElement(*parent, anonymousBlock); |
| 272 | // anonymousBlock is dead here. |
| 273 | } |
| 274 | |
| 275 | RenderPtr<RenderObject> RenderTreeBuilder::Block::detach(RenderBlock& parent, RenderObject& oldChild, CanCollapseAnonymousBlock canCollapseAnonymousBlock) |
| 276 | { |
| 277 | // No need to waste time in merging or removing empty anonymous blocks. |
| 278 | // We can just bail out if our document is getting destroyed. |
| 279 | if (parent.renderTreeBeingDestroyed()) |
| 280 | return m_builder.detachFromRenderElement(parent, oldChild); |
| 281 | |
| 282 | // If this child is a block, and if our previous and next siblings are both anonymous blocks |
| 283 | // with inline content, then we can fold the inline content back together. |
| 284 | auto prev = makeWeakPtr(oldChild.previousSibling()); |
| 285 | auto next = makeWeakPtr(oldChild.nextSibling()); |
| 286 | bool canMergeAnonymousBlocks = canMergeContiguousAnonymousBlocks(oldChild, prev.get(), next.get()); |
| 287 | |
| 288 | parent.invalidateLineLayoutPath(); |
| 289 | |
| 290 | auto takenChild = m_builder.detachFromRenderElement(parent, oldChild); |
| 291 | |
| 292 | if (canMergeAnonymousBlocks && prev && next) { |
| 293 | prev->setNeedsLayoutAndPrefWidthsRecalc(); |
| 294 | RenderBlock& nextBlock = downcast<RenderBlock>(*next); |
| 295 | RenderBlock& prevBlock = downcast<RenderBlock>(*prev); |
| 296 | |
| 297 | if (prev->childrenInline() != next->childrenInline()) { |
| 298 | RenderBlock& inlineChildrenBlock = prev->childrenInline() ? prevBlock : nextBlock; |
| 299 | RenderBlock& blockChildrenBlock = prev->childrenInline() ? nextBlock : prevBlock; |
| 300 | |
| 301 | // Place the inline children block inside of the block children block instead of deleting it. |
| 302 | // In order to reuse it, we have to reset it to just be a generic anonymous block. Make sure |
| 303 | // to clear out inherited column properties by just making a new style, and to also clear the |
| 304 | // column span flag if it is set. |
| 305 | ASSERT(!inlineChildrenBlock.continuation()); |
| 306 | // Cache this value as it might get changed in setStyle() call. |
| 307 | inlineChildrenBlock.setStyle(RenderStyle::createAnonymousStyleWithDisplay(parent.style(), DisplayType::Block)); |
| 308 | auto blockToMove = m_builder.detachFromRenderElement(parent, inlineChildrenBlock); |
| 309 | |
| 310 | // Now just put the inlineChildrenBlock inside the blockChildrenBlock. |
| 311 | RenderObject* beforeChild = prev == &inlineChildrenBlock ? blockChildrenBlock.firstChild() : nullptr; |
| 312 | m_builder.attachToRenderElementInternal(blockChildrenBlock, WTFMove(blockToMove), beforeChild); |
| 313 | next->setNeedsLayoutAndPrefWidthsRecalc(); |
| 314 | |
| 315 | // inlineChildrenBlock got reparented to blockChildrenBlock, so it is no longer a child |
| 316 | // of "this". we null out prev or next so that is not used later in the function. |
| 317 | if (&inlineChildrenBlock == &prevBlock) |
| 318 | prev = nullptr; |
| 319 | else |
| 320 | next = nullptr; |
| 321 | } else { |
| 322 | // Take all the children out of the |next| block and put them in |
| 323 | // the |prev| block. |
| 324 | m_builder.moveAllChildrenIncludingFloats(nextBlock, prevBlock, RenderTreeBuilder::NormalizeAfterInsertion::No); |
| 325 | |
| 326 | // Delete the now-empty block's lines and nuke it. |
| 327 | nextBlock.deleteLines(); |
| 328 | m_builder.destroy(nextBlock); |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | if (canCollapseAnonymousBlock == CanCollapseAnonymousBlock::Yes && parent.canDropAnonymousBlockChild()) { |
| 333 | RenderObject* child = prev ? prev.get() : next.get(); |
| 334 | if (canMergeAnonymousBlocks && child && !child->previousSibling() && !child->nextSibling()) { |
| 335 | // The removal has knocked us down to containing only a single anonymous box. We can pull the content right back up into our box. |
| 336 | dropAnonymousBoxChild(parent, downcast<RenderBlock>(*child)); |
| 337 | } else if ((prev && prev->isAnonymousBlock()) || (next && next->isAnonymousBlock())) { |
| 338 | // It's possible that the removal has knocked us down to a single anonymous block with floating siblings. |
| 339 | RenderBlock& anonBlock = downcast<RenderBlock>((prev && prev->isAnonymousBlock()) ? *prev : *next); |
| 340 | if (canDropAnonymousBlock(anonBlock)) { |
| 341 | bool dropAnonymousBlock = true; |
| 342 | for (auto& sibling : childrenOfType<RenderObject>(parent)) { |
| 343 | if (&sibling == &anonBlock) |
| 344 | continue; |
| 345 | if (!sibling.isFloating()) { |
| 346 | dropAnonymousBlock = false; |
| 347 | break; |
| 348 | } |
| 349 | } |
| 350 | if (dropAnonymousBlock) |
| 351 | dropAnonymousBoxChild(parent, anonBlock); |
| 352 | } |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | if (!parent.firstChild()) { |
| 357 | // If this was our last child be sure to clear out our line boxes. |
| 358 | if (parent.childrenInline()) |
| 359 | parent.deleteLines(); |
| 360 | } |
| 361 | return takenChild; |
| 362 | } |
| 363 | |
| 364 | void RenderTreeBuilder::Block::dropAnonymousBoxChild(RenderBlock& parent, RenderBlock& child) |
| 365 | { |
| 366 | parent.setNeedsLayoutAndPrefWidthsRecalc(); |
| 367 | parent.setChildrenInline(child.childrenInline()); |
| 368 | auto* nextSibling = child.nextSibling(); |
| 369 | |
| 370 | auto toBeDeleted = m_builder.detachFromRenderElement(parent, child); |
| 371 | m_builder.moveAllChildren(child, parent, nextSibling, RenderTreeBuilder::NormalizeAfterInsertion::No); |
| 372 | // Delete the now-empty block's lines and nuke it. |
| 373 | child.deleteLines(); |
| 374 | } |
| 375 | |
| 376 | RenderPtr<RenderObject> RenderTreeBuilder::Block::detach(RenderBlockFlow& parent, RenderObject& child, CanCollapseAnonymousBlock canCollapseAnonymousBlock) |
| 377 | { |
| 378 | if (!parent.renderTreeBeingDestroyed()) { |
| 379 | auto* fragmentedFlow = parent.multiColumnFlow(); |
| 380 | if (fragmentedFlow && fragmentedFlow != &child) |
| 381 | m_builder.multiColumnBuilder().multiColumnRelativeWillBeRemoved(*fragmentedFlow, child); |
| 382 | } |
| 383 | return detach(static_cast<RenderBlock&>(parent), child, canCollapseAnonymousBlock); |
| 384 | } |
| 385 | |
| 386 | } |
| 387 | |