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 | |