1/*
2 * Copyright (C) 2018 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 "LayoutTreeBuilder.h"
28
29#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31#include "DisplayBox.h"
32#include "InlineFormattingState.h"
33#include "LayoutBlockContainer.h"
34#include "LayoutBox.h"
35#include "LayoutChildIterator.h"
36#include "LayoutContainer.h"
37#include "LayoutInlineBox.h"
38#include "LayoutInlineContainer.h"
39#include "LayoutState.h"
40#include "RenderBlock.h"
41#include "RenderChildIterator.h"
42#include "RenderElement.h"
43#include "RenderImage.h"
44#include "RenderInline.h"
45#include "RenderStyle.h"
46#include "RenderView.h"
47#include <wtf/text/TextStream.h>
48
49namespace WebCore {
50namespace Layout {
51
52std::unique_ptr<Container> TreeBuilder::createLayoutTree(const RenderView& renderView)
53{
54 auto style = RenderStyle::clone(renderView.style());
55 style.setLogicalWidth(Length(renderView.width(), Fixed));
56 style.setLogicalHeight(Length(renderView.height(), Fixed));
57
58 std::unique_ptr<Container> initialContainingBlock(new BlockContainer(WTF::nullopt, WTFMove(style)));
59 TreeBuilder::createSubTree(renderView, *initialContainingBlock);
60 return initialContainingBlock;
61}
62
63void TreeBuilder::createSubTree(const RenderElement& rootRenderer, Container& rootContainer)
64{
65 auto elementAttributes = [] (const RenderElement& renderer) -> Optional<Box::ElementAttributes> {
66 if (renderer.isDocumentElementRenderer())
67 return Box::ElementAttributes { Box::ElementType::Document };
68 if (auto* element = renderer.element()) {
69 if (element->hasTagName(HTMLNames::bodyTag))
70 return Box::ElementAttributes { Box::ElementType::Body };
71 if (element->hasTagName(HTMLNames::colTag))
72 return Box::ElementAttributes { Box::ElementType::TableColumn };
73 if (element->hasTagName(HTMLNames::trTag))
74 return Box::ElementAttributes { Box::ElementType::TableRow };
75 if (element->hasTagName(HTMLNames::colgroupTag))
76 return Box::ElementAttributes { Box::ElementType::TableColumnGroup };
77 if (element->hasTagName(HTMLNames::tbodyTag))
78 return Box::ElementAttributes { Box::ElementType::TableRowGroup };
79 if (element->hasTagName(HTMLNames::theadTag))
80 return Box::ElementAttributes { Box::ElementType::TableHeaderGroup };
81 if (element->hasTagName(HTMLNames::tfootTag))
82 return Box::ElementAttributes { Box::ElementType::TableFooterGroup };
83 if (element->hasTagName(HTMLNames::tfootTag))
84 return Box::ElementAttributes { Box::ElementType::TableFooterGroup };
85 if (element->hasTagName(HTMLNames::imgTag))
86 return Box::ElementAttributes { Box::ElementType::Image };
87 if (element->hasTagName(HTMLNames::iframeTag))
88 return Box::ElementAttributes { Box::ElementType::IFrame };
89 return Box::ElementAttributes { Box::ElementType::GenericElement };
90 }
91 return WTF::nullopt;
92 };
93
94 for (auto& child : childrenOfType<RenderObject>(rootRenderer)) {
95 std::unique_ptr<Box> box;
96
97 if (is<RenderText>(child)) {
98 box = std::make_unique<InlineBox>(Optional<Box::ElementAttributes>(), RenderStyle::createAnonymousStyleWithDisplay(rootRenderer.style(), DisplayType::Inline));
99 downcast<InlineBox>(*box).setTextContent(downcast<RenderText>(child).originalText());
100 } else if (is<RenderReplaced>(child)) {
101 auto& renderer = downcast<RenderReplaced>(child);
102 auto display = renderer.style().display();
103 if (display == DisplayType::Block)
104 box = std::make_unique<Box>(elementAttributes(renderer), RenderStyle::clone(renderer.style()));
105 else
106 box = std::make_unique<InlineBox>(elementAttributes(renderer), RenderStyle::clone(renderer.style()));
107 // FIXME: We don't yet support all replaced elements and this is temporary anyway.
108 if (box->replaced())
109 box->replaced()->setIntrinsicSize(renderer.intrinsicSize());
110 if (is<RenderImage>(renderer)) {
111 auto& imageRenderer = downcast<RenderImage>(renderer);
112 if (imageRenderer.imageResource().errorOccurred())
113 box->replaced()->setIntrinsicRatio(1);
114 }
115 } else if (is<RenderElement>(child)) {
116 auto& renderer = downcast<RenderElement>(child);
117 auto display = renderer.style().display();
118 if (display == DisplayType::Block)
119 box = std::make_unique<BlockContainer>(elementAttributes(renderer), RenderStyle::clone(renderer.style()));
120 else if (display == DisplayType::Inline)
121 box = std::make_unique<InlineContainer>(elementAttributes(renderer), RenderStyle::clone(renderer.style()));
122 else if (display == DisplayType::InlineBlock)
123 box = std::make_unique<InlineContainer>(elementAttributes(renderer), RenderStyle::clone(renderer.style()));
124 else {
125 ASSERT_NOT_IMPLEMENTED_YET();
126 continue;
127 }
128 } else {
129 ASSERT_NOT_IMPLEMENTED_YET();
130 continue;
131 }
132
133 if (!rootContainer.hasChild()) {
134 rootContainer.setFirstChild(*box);
135 rootContainer.setLastChild(*box);
136 } else {
137 auto* lastChild = const_cast<Box*>(rootContainer.lastChild());
138 box->setPreviousSibling(*lastChild);
139 lastChild->setNextSibling(*box);
140 rootContainer.setLastChild(*box);
141 }
142
143 box->setParent(rootContainer);
144
145 if (box->isOutOfFlowPositioned()) {
146 // Not efficient, but this is temporary anyway.
147 // Collect the out-of-flow descendants at the formatting root level (as opposed to at the containing block level, though they might be the same).
148 const_cast<Container&>(box->formattingContextRoot()).addOutOfFlowDescendant(*box);
149 }
150 if (is<Container>(*box))
151 createSubTree(downcast<RenderElement>(child), downcast<Container>(*box));
152 // Temporary
153 box.release();
154 }
155}
156
157#if ENABLE(TREE_DEBUGGING)
158static void outputInlineRuns(TextStream& stream, const LayoutState& layoutState, const Container& inlineFormattingRoot, unsigned depth)
159{
160 auto& inlineFormattingState = layoutState.establishedFormattingState(inlineFormattingRoot);
161 ASSERT(is<InlineFormattingState>(inlineFormattingState));
162 auto& inlineRuns = downcast<InlineFormattingState>(inlineFormattingState).inlineRuns();
163
164 for (auto& inlineRun : inlineRuns) {
165 unsigned printedCharacters = 0;
166 while (++printedCharacters <= depth * 2)
167 stream << " ";
168 stream << "run";
169 if (inlineRun.textContext())
170 stream << "(" << inlineRun.textContext()->start() << ", " << inlineRun.textContext()->start() + inlineRun.textContext()->length() << ") ";
171 else
172 stream << "(x, x) ";
173 stream << "at [" << inlineRun.logicalLeft() << ", " << inlineRun.logicalTop() << "] size [" << inlineRun.logicalWidth() << " " << inlineRun.logicalHeight() << "]";
174 stream.nextLine();
175 }
176}
177
178static void outputLayoutBox(TextStream& stream, const Box& layoutBox, const Display::Box* displayBox, unsigned depth)
179{
180 unsigned printedCharacters = 0;
181 while (++printedCharacters <= depth * 2)
182 stream << " ";
183
184 if (is<InlineContainer>(layoutBox))
185 stream << "inline container";
186 else if (is<InlineBox>(layoutBox)) {
187 if (layoutBox.replaced())
188 stream << "inline replaced box";
189 else if (downcast<InlineBox>(layoutBox).hasTextContent()) {
190 auto textContent = downcast<InlineBox>(layoutBox).textContent();
191 stream << "inline text [\"" << textContent.utf8().data() << "\"]";
192 } else
193 stream << "inline box";
194 } else if (is<BlockContainer>(layoutBox)) {
195 if (!layoutBox.parent())
196 stream << "initial ";
197 stream << "block container";
198 } else if (layoutBox.isBlockLevelBox())
199 stream << "block box";
200 else
201 stream << "box";
202 // FIXME: Inline text runs don't create display boxes yet.
203 if (displayBox) {
204 // FIXME: display box is not completely set yet.
205 if ((is<InlineBox>(layoutBox) || is<InlineContainer>(layoutBox)) && !layoutBox.isFloatingPositioned())
206 stream << " at [" << "." << " " << "." << "] size [" << displayBox->width() << " " << displayBox->height() << "]";
207 else
208 stream << " at [" << displayBox->left() << " " << displayBox->top() << "] size [" << displayBox->width() << " " << displayBox->height() << "]";
209 }
210 stream << " [" << &layoutBox << "]";
211
212 stream.nextLine();
213}
214
215static void outputLayoutTree(const LayoutState* layoutState, TextStream& stream, const Container& rootContainer, unsigned depth)
216{
217 for (auto& child : childrenOfType<Box>(rootContainer)) {
218 Display::Box* displayBox = nullptr;
219 // Not all boxes generate display boxes.
220 if (layoutState && layoutState->hasDisplayBox(child))
221 displayBox = &layoutState->displayBoxForLayoutBox(child);
222
223 outputLayoutBox(stream, child, displayBox, depth);
224 if (layoutState && child.establishesInlineFormattingContext())
225 outputInlineRuns(stream, *layoutState, downcast<Container>(child), depth + 1);
226
227 if (is<Container>(child))
228 outputLayoutTree(layoutState, stream, downcast<Container>(child), depth + 1);
229 }
230}
231
232void showLayoutTree(const Box& layoutBox, const LayoutState* layoutState)
233{
234 TextStream stream(TextStream::LineMode::MultipleLine, TextStream::Formatting::SVGStyleRect);
235
236 auto& initialContainingBlock = layoutBox.initialContainingBlock();
237 outputLayoutBox(stream, initialContainingBlock, layoutState ? &layoutState->displayBoxForLayoutBox(initialContainingBlock) : nullptr, 0);
238 outputLayoutTree(layoutState, stream, initialContainingBlock, 1);
239 WTFLogAlways("%s", stream.release().utf8().data());
240}
241
242void showLayoutTree(const Box& layoutBox)
243{
244 showLayoutTree(layoutBox, nullptr);
245}
246
247void printLayoutTreeForLiveDocuments()
248{
249 for (const auto* document : Document::allDocuments()) {
250 if (!document->renderView())
251 continue;
252 if (document->frame() && document->frame()->isMainFrame())
253 fprintf(stderr, "----------------------main frame--------------------------\n");
254 fprintf(stderr, "%s\n", document->url().string().utf8().data());
255 // FIXME: Need to find a way to output geometry without layout context.
256 // Layout::TreeBuilder::showLayoutTree(*TreeBuilder::createLayoutTree(*document->renderView()));
257 }
258}
259#endif
260
261}
262}
263
264#endif
265