[LFC][IFC] Add generic inline line breaker
[WebKit-https.git] / Source / WebCore / layout / Verification.cpp
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 "LayoutContext.h"
28
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31 #include "DisplayBox.h"
32 #include "InlineTextBox.h"
33 #include "LayoutBox.h"
34 #include "LayoutContainer.h"
35 #include "LayoutTreeBuilder.h"
36 #include "RenderBox.h"
37 #include "RenderView.h"
38 #include <wtf/text/TextStream.h>
39
40 namespace WebCore {
41 namespace Layout {
42
43 static bool outputMismatchingSimpleLineInformationIfNeeded(TextStream& stream, const LayoutContext& layoutContext, const RenderBlockFlow& blockFlow, const Container& inlineFormattingRoot)
44 {
45     auto* lineLayoutData = blockFlow.simpleLineLayout();
46     if (!lineLayoutData) {
47         ASSERT_NOT_REACHED();
48         return true;
49     }
50
51     auto& inlineFormattingState = const_cast<LayoutContext&>(layoutContext).establishedFormattingState(inlineFormattingRoot);
52     ASSERT(is<InlineFormattingState>(inlineFormattingState));
53     auto& layoutRuns = downcast<InlineFormattingState>(inlineFormattingState).layoutRuns();
54
55     if (layoutRuns.size() != lineLayoutData->runCount()) {
56         stream << "Mismatching number of runs: simple runs(" << lineLayoutData->runCount() << ") layout runs(" << layoutRuns.size() << ")";
57         stream.nextLine();
58         return true;
59     }
60
61     auto mismatched = false;
62     for (unsigned i = 0; i < lineLayoutData->runCount(); ++i) {
63         auto& simpleRun = lineLayoutData->runAt(i);
64         auto& layoutRun = layoutRuns[i];
65
66         if (simpleRun.start == layoutRun.start() && simpleRun.end == layoutRun.end() && simpleRun.logicalLeft == layoutRun.left() && simpleRun.logicalRight == layoutRun.right())
67             continue;
68
69         stream << "Mismatching: simple run(" << simpleRun.start << ", " << simpleRun.end << ") (" << simpleRun.logicalLeft << ", " << simpleRun.logicalRight << ") layout run(" << layoutRun.start() << ", " << layoutRun.end() << ") (" << layoutRun.left() << ", " << layoutRun.right() << ")";
70         stream.nextLine();
71         mismatched = true;
72     }
73     return mismatched;
74 }
75
76 static bool outputMismatchingComplexLineInformationIfNeeded(TextStream& stream, const LayoutContext& layoutContext, const RenderBlockFlow& blockFlow, const Container& inlineFormattingRoot)
77 {
78     auto& inlineFormattingState = const_cast<LayoutContext&>(layoutContext).establishedFormattingState(inlineFormattingRoot);
79     ASSERT(is<InlineFormattingState>(inlineFormattingState));
80     auto& layoutRuns = downcast<InlineFormattingState>(inlineFormattingState).layoutRuns();
81
82     auto mismatched = false;
83     unsigned layoutRunIndex = 0;
84     for (auto* rootLine = blockFlow.firstRootBox(); rootLine; rootLine = rootLine->nextRootBox()) {
85         for (auto* lineBox = rootLine->firstChild(); lineBox; lineBox = lineBox->nextOnLine()) {
86             if (!is<InlineTextBox>(lineBox))
87                 continue;
88
89             if (layoutRunIndex >= layoutRuns.size()) {
90                 // FIXME: <span>foobar</span>foobar generates 2 inline text boxes while we only generate one layout run (which is much better, since it enables us to do kerning across inline elements).
91                 stream << "Mismatching number of runs: layout runs(" << layoutRuns.size() << ")";
92                 stream.nextLine();
93                 return true;
94             }
95
96             auto& layoutRun = layoutRuns[layoutRunIndex];
97             auto& inlineTextBox = downcast<InlineTextBox>(*lineBox);
98             if (inlineTextBox.start() == layoutRun.start() && inlineTextBox.end() == layoutRun.end() && inlineTextBox.logicalLeft() == layoutRun.left() && inlineTextBox.logicalRight() == layoutRun.right()) {
99                 stream << "Mismatching: simple run(" << inlineTextBox.start() << ", " << inlineTextBox.end() << ") (" << inlineTextBox.logicalLeft() << ", " << inlineTextBox.logicalRight() << ") layout run(" << layoutRun.start() << ", " << layoutRun.end() << ") (" << layoutRun.left() << ", " << layoutRun.right() << ")";
100                 stream.nextLine();
101                 mismatched = true;
102             }
103             ++layoutRunIndex;
104         }
105     }
106     return mismatched;
107 }
108
109 static bool outputMismatchingBlockBoxInformationIfNeeded(TextStream& stream, const LayoutContext& context, const RenderBox& renderer, const Box& layoutBox)
110 {
111     bool firstMismatchingRect = true;
112     auto outputRect = [&] (const String& prefix, const LayoutRect& rendererRect, const LayoutRect& layoutRect) {
113         if (firstMismatchingRect) {
114             stream << (renderer.element() ? renderer.element()->nodeName().utf8().data() : "") << " " << renderer.renderName() << "(" << &renderer << ") layoutBox(" << &layoutBox << ")";
115             stream.nextLine();
116             firstMismatchingRect = false;
117         }
118
119         stream  << prefix.utf8().data() << "\trenderer->(" << rendererRect.x() << "," << rendererRect.y() << ") (" << rendererRect.width() << "x" << rendererRect.height() << ")"
120             << "\tlayout->(" << layoutRect.x() << "," << layoutRect.y() << ") (" << layoutRect.width() << "x" << layoutRect.height() << ")"; 
121         stream.nextLine();
122     };
123
124     auto renderBoxLikeMarginBox = [](auto& displayBox) {
125         // Produce a RenderBox matching margin box.
126         auto borderBox = displayBox.borderBox();
127
128         return Display::Box::Rect {
129             borderBox.top() - displayBox.nonCollapsedMarginTop(),
130             borderBox.left() - displayBox.nonComputedMarginLeft(),
131             displayBox.nonComputedMarginLeft() + borderBox.width() + displayBox.nonComputedMarginRight(),
132             displayBox.nonCollapsedMarginTop() + borderBox.height() + displayBox.nonCollapsedMarginBottom()
133         };
134     };
135
136     auto& displayBox = context.displayBoxForLayoutBox(layoutBox);
137
138     auto frameRect = renderer.frameRect();
139     // rendering does not offset for relative positioned boxes.
140     if (renderer.isInFlowPositioned())
141         frameRect.move(renderer.offsetForInFlowPosition());
142
143     if (frameRect != displayBox.rect()) {
144         outputRect("frameBox", renderer.frameRect(), displayBox.rect());
145         return true;
146     }
147
148     if (renderer.marginBoxRect() != renderBoxLikeMarginBox(displayBox)) {
149         outputRect("marginBox", renderer.marginBoxRect(), renderBoxLikeMarginBox(displayBox));
150         return true;
151     }
152
153     if (renderer.borderBoxRect() != displayBox.borderBox()) {
154         outputRect("borderBox", renderer.borderBoxRect(), displayBox.borderBox());
155         return true;
156     }
157
158     if (renderer.paddingBoxRect() != displayBox.paddingBox()) {
159         outputRect("paddingBox", renderer.paddingBoxRect(), displayBox.paddingBox());
160         return true;
161     }
162
163     if (renderer.contentBoxRect() != displayBox.contentBox()) {
164         outputRect("contentBox", renderer.contentBoxRect(), displayBox.contentBox());
165         return true;
166     }
167
168     return false;
169 }
170
171 static bool verifyAndOutputSubtree(TextStream& stream, const LayoutContext& context, const RenderBox& renderer, const Box& layoutBox)
172 {
173     auto mismtachingGeometry = outputMismatchingBlockBoxInformationIfNeeded(stream, context, renderer, layoutBox);
174
175     if (!is<Container>(layoutBox))
176         return mismtachingGeometry;
177
178     auto& container = downcast<Container>(layoutBox);
179     auto* childBox = container.firstChild();
180     auto* childRenderer = renderer.firstChild();
181
182     while (childRenderer) {
183         if (!is<RenderBox>(*childRenderer)) {
184             childRenderer = childRenderer->nextSibling();
185             continue;
186         }
187
188         if (!childBox) {
189             stream  << "Trees are out of sync!";
190             stream.nextLine();
191             return true;
192         }
193
194         if (is<RenderBlockFlow>(*childRenderer) && childBox->establishesInlineFormattingContext()) {
195             ASSERT(childRenderer->childrenInline());
196             auto& blockFlow = downcast<RenderBlockFlow>(*childRenderer);
197             auto& formattingRoot = downcast<Container>(*childBox);
198             mismtachingGeometry |= blockFlow.lineLayoutPath() == RenderBlockFlow::SimpleLinesPath ? outputMismatchingSimpleLineInformationIfNeeded(stream, context, blockFlow, formattingRoot) : outputMismatchingComplexLineInformationIfNeeded(stream, context, blockFlow, formattingRoot);
199         } else {
200             auto mismatchingSubtreeGeometry = verifyAndOutputSubtree(stream, context, downcast<RenderBox>(*childRenderer), *childBox);
201             mismtachingGeometry |= mismatchingSubtreeGeometry;
202         }
203
204         childBox = childBox->nextSibling();
205         childRenderer = childRenderer->nextSibling();
206     }
207
208     return mismtachingGeometry;
209 }
210
211 void LayoutContext::verifyAndOutputMismatchingLayoutTree(const RenderView& renderView) const
212 {
213     TextStream stream;
214     auto mismatchingGeometry = verifyAndOutputSubtree(stream, *this, renderView, *m_root.get());
215     if (!mismatchingGeometry)
216         return;
217 #if ENABLE(TREE_DEBUGGING)
218     showRenderTree(&renderView);
219     showLayoutTree(*m_root.get(), this);
220 #endif
221     WTFLogAlways("%s", stream.release().utf8().data());
222     ASSERT_NOT_REACHED();
223 }
224
225 }
226 }
227
228 #endif