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
38namespace WebCore {
39
40static void moveAllChildrenToInternal(RenderBoxModelObject& from, RenderElement& newParent)
41{
42 while (from.firstChild())
43 newParent.attachRendererInternal(from.detachRendererInternal(*from.firstChild()), &from);
44}
45
46static 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
55static 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
82static 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
105RenderTreeBuilder::Block::Block(RenderTreeBuilder& builder)
106 : m_builder(builder)
107{
108}
109
110void 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
118void 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
160void 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
248void 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
256void 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
275RenderPtr<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
364void 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
376RenderPtr<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