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 "LayoutState.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 "RenderInline.h"
38#include "RenderView.h"
39#include <wtf/text/TextStream.h>
40
41namespace WebCore {
42namespace Layout {
43
44static bool areEssentiallyEqual(float a, LayoutUnit b)
45{
46 if (a == b.toFloat())
47 return true;
48
49 return fabs(a - b.toFloat()) <= 8 * LayoutUnit::epsilon();
50}
51
52static bool outputMismatchingSimpleLineInformationIfNeeded(TextStream& stream, const LayoutState& layoutState, const RenderBlockFlow& blockFlow, const Container& inlineFormattingRoot)
53{
54 auto* lineLayoutData = blockFlow.simpleLineLayout();
55 if (!lineLayoutData) {
56 ASSERT_NOT_REACHED();
57 return true;
58 }
59
60 auto& inlineFormattingState = layoutState.establishedFormattingState(inlineFormattingRoot);
61 ASSERT(is<InlineFormattingState>(inlineFormattingState));
62 auto& inlineRunList = downcast<InlineFormattingState>(inlineFormattingState).inlineRuns();
63
64 if (inlineRunList.size() != lineLayoutData->runCount()) {
65 stream << "Mismatching number of runs: simple runs(" << lineLayoutData->runCount() << ") inline runs(" << inlineRunList.size() << ")";
66 stream.nextLine();
67 return true;
68 }
69
70 auto mismatched = false;
71 for (unsigned i = 0; i < lineLayoutData->runCount(); ++i) {
72 auto& simpleRun = lineLayoutData->runAt(i);
73 auto& inlineRun = inlineRunList[i];
74
75 auto matchingRuns = areEssentiallyEqual(simpleRun.logicalLeft, inlineRun.logicalLeft()) && areEssentiallyEqual(simpleRun.logicalRight, inlineRun.logicalRight());
76 if (matchingRuns)
77 matchingRuns = (simpleRun.start == inlineRun.textContext()->start() && simpleRun.end == (inlineRun.textContext()->start() + inlineRun.textContext()->length()));
78 if (matchingRuns)
79 continue;
80
81 stream << "Mismatching: simple run(" << simpleRun.start << ", " << simpleRun.end << ") (" << simpleRun.logicalLeft << ", " << simpleRun.logicalRight << ") layout run(" << inlineRun.textContext()->start() << ", " << inlineRun.textContext()->start() + inlineRun.textContext()->length() << ") (" << inlineRun.logicalLeft() << ", " << inlineRun.logicalRight() << ")";
82 stream.nextLine();
83 mismatched = true;
84 }
85 return mismatched;
86}
87
88static bool checkForMatchingNonTextRuns(const InlineRun& inlineRun, const WebCore::InlineBox& inlineBox)
89{
90 return areEssentiallyEqual(inlineBox.logicalLeft(), inlineRun.logicalLeft())
91 && areEssentiallyEqual(inlineBox.logicalRight(), inlineRun.logicalRight())
92 && areEssentiallyEqual(inlineBox.logicalHeight(), inlineRun.logicalHeight());
93}
94
95static bool checkForMatchingTextRuns(const InlineRun& inlineRun, float logicalLeft, float logicalRight, unsigned start, unsigned end, float logicalHeight)
96{
97 return areEssentiallyEqual(logicalLeft, inlineRun.logicalLeft())
98 && areEssentiallyEqual(logicalRight, inlineRun.logicalRight())
99 && start == inlineRun.textContext()->start()
100 && (end == (inlineRun.textContext()->start() + inlineRun.textContext()->length()))
101 && areEssentiallyEqual(logicalHeight, inlineRun.logicalHeight());
102}
103
104static void collectFlowBoxSubtree(const InlineFlowBox& flowbox, Vector<WebCore::InlineBox*>& inlineBoxes)
105{
106 auto* inlineBox = flowbox.firstLeafChild();
107 auto* lastLeafChild = flowbox.lastLeafChild();
108 while (inlineBox) {
109 inlineBoxes.append(inlineBox);
110 if (inlineBox == lastLeafChild)
111 break;
112 inlineBox = inlineBox->nextLeafChild();
113 }
114}
115
116static void collectInlineBoxes(const RenderBlockFlow& root, Vector<WebCore::InlineBox*>& inlineBoxes)
117{
118 for (auto* rootLine = root.firstRootBox(); rootLine; rootLine = rootLine->nextRootBox()) {
119 for (auto* inlineBox = rootLine->firstChild(); inlineBox; inlineBox = inlineBox->nextOnLine()) {
120 if (!is<InlineFlowBox>(inlineBox)) {
121 inlineBoxes.append(inlineBox);
122 continue;
123 }
124 collectFlowBoxSubtree(downcast<InlineFlowBox>(*inlineBox), inlineBoxes);
125 }
126 }
127}
128
129static LayoutUnit resolveForRelativePositionIfNeeded(const InlineTextBox& inlineTextBox)
130{
131 LayoutUnit xOffset;
132 auto* parent = inlineTextBox.parent();
133 while (is<InlineFlowBox>(parent)) {
134 auto& renderer = parent->renderer();
135 if (renderer.isInFlowPositioned())
136 xOffset = renderer.offsetForInFlowPosition().width();
137 parent = parent->parent();
138 }
139 return xOffset;
140}
141
142static bool outputMismatchingComplexLineInformationIfNeeded(TextStream& stream, const LayoutState& layoutState, const RenderBlockFlow& blockFlow, const Container& inlineFormattingRoot)
143{
144 auto& inlineFormattingState = layoutState.establishedFormattingState(inlineFormattingRoot);
145 ASSERT(is<InlineFormattingState>(inlineFormattingState));
146 auto& inlineRunList = downcast<InlineFormattingState>(inlineFormattingState).inlineRuns();
147
148 // Collect inlineboxes.
149 Vector<WebCore::InlineBox*> inlineBoxes;
150 collectInlineBoxes(blockFlow, inlineBoxes);
151
152 auto mismatched = false;
153 unsigned runIndex = 0;
154
155 if (inlineBoxes.size() != inlineRunList.size()) {
156 stream << "Warning: mismatching number of runs: inlineboxes(" << inlineBoxes.size() << ") vs. inline runs(" << inlineRunList.size() << ")";
157 stream.nextLine();
158 }
159
160 for (unsigned inlineBoxIndex = 0; inlineBoxIndex < inlineBoxes.size() && runIndex < inlineRunList.size(); ++inlineBoxIndex) {
161 auto* inlineBox = inlineBoxes[inlineBoxIndex];
162 auto* inlineTextBox = is<InlineTextBox>(inlineBox) ? downcast<InlineTextBox>(inlineBox) : nullptr;
163
164 auto& inlineRun = inlineRunList[runIndex];
165 auto matchingRuns = false;
166 if (inlineTextBox) {
167 auto xOffset = resolveForRelativePositionIfNeeded(*inlineTextBox);
168 matchingRuns = checkForMatchingTextRuns(inlineRun, inlineTextBox->logicalLeft() + xOffset,
169 inlineTextBox->logicalRight() + xOffset,
170 inlineTextBox->start(),
171 inlineTextBox->end() + 1,
172 inlineTextBox->logicalHeight());
173
174 // <span>foobar</span>foobar generates 2 inline text boxes while we only generate one inline run.
175 // also <div>foo<img style="float: left;">bar</div> too.
176 auto inlineRunEnd = inlineRun.textContext()->start() + inlineRun.textContext()->length();
177 auto textRunMightBeExtended = !matchingRuns && inlineTextBox->end() < inlineRunEnd && inlineBoxIndex < inlineBoxes.size() - 1;
178
179 if (textRunMightBeExtended) {
180 auto logicalLeft = inlineTextBox->logicalLeft() + xOffset;
181 auto logicalRight = inlineTextBox->logicalRight() + xOffset;
182 auto start = inlineTextBox->start();
183 auto end = inlineTextBox->end() + 1;
184 auto index = ++inlineBoxIndex;
185 for (; index < inlineBoxes.size(); ++index) {
186 auto* inlineBox = inlineBoxes[index];
187 auto* inlineTextBox = is<InlineTextBox>(inlineBox) ? downcast<InlineTextBox>(inlineBox) : nullptr;
188 // Can't mix different inline boxes.
189 if (!inlineTextBox)
190 break;
191
192 auto xOffset = resolveForRelativePositionIfNeeded(*inlineTextBox);
193 logicalRight = inlineTextBox->logicalRight() + xOffset;
194 end += (inlineTextBox->end() + 1);
195 if (checkForMatchingTextRuns(inlineRun, logicalLeft, logicalRight, start, end, inlineTextBox->logicalHeight())) {
196 matchingRuns = true;
197 inlineBoxIndex = index;
198 break;
199 }
200
201 // Went too far?
202 if (end >= inlineRunEnd)
203 break;
204 }
205 }
206 } else
207 matchingRuns = checkForMatchingNonTextRuns(inlineRun, *inlineBox);
208
209
210 if (!matchingRuns) {
211 stream << "Mismatching: run ";
212
213 if (inlineTextBox)
214 stream << "(" << inlineTextBox->start() << ", " << inlineTextBox->end() + 1 << ")";
215 stream << " (" << inlineBox->logicalLeft() << ", " << inlineBox->logicalRight() << ") (" << inlineBox->logicalWidth() << "x" << inlineBox->logicalHeight() << ")";
216
217 stream << "inline run ";
218 if (inlineRun.textContext())
219 stream << "(" << inlineRun.textContext()->start() << ", " << inlineRun.textContext()->start() + inlineRun.textContext()->length() << ") ";
220 stream << "(" << inlineRun.logicalLeft() << ", " << inlineRun.logicalRight() << ") (" << inlineRun.logicalWidth() << "x" << inlineRun.logicalHeight() << ")";
221 stream.nextLine();
222 mismatched = true;
223 }
224 ++runIndex;
225 }
226 return mismatched;
227}
228
229static bool outputMismatchingBlockBoxInformationIfNeeded(TextStream& stream, const LayoutState& context, const RenderBox& renderer, const Box& layoutBox)
230{
231 bool firstMismatchingRect = true;
232 auto outputRect = [&] (const String& prefix, const LayoutRect& rendererRect, const LayoutRect& layoutRect) {
233 if (firstMismatchingRect) {
234 stream << (renderer.element() ? renderer.element()->nodeName().utf8().data() : "") << " " << renderer.renderName() << "(" << &renderer << ") layoutBox(" << &layoutBox << ")";
235 stream.nextLine();
236 firstMismatchingRect = false;
237 }
238
239 stream << prefix.utf8().data() << "\trenderer->(" << rendererRect.x() << "," << rendererRect.y() << ") (" << rendererRect.width() << "x" << rendererRect.height() << ")"
240 << "\tlayout->(" << layoutRect.x() << "," << layoutRect.y() << ") (" << layoutRect.width() << "x" << layoutRect.height() << ")";
241 stream.nextLine();
242 };
243
244 auto renderBoxLikeMarginBox = [](auto& displayBox) {
245 // Produce a RenderBox matching margin box.
246 auto borderBox = displayBox.borderBox();
247
248 return Display::Box::Rect {
249 borderBox.top() - displayBox.nonCollapsedMarginBefore(),
250 borderBox.left() - displayBox.computedMarginStart().valueOr(0),
251 displayBox.computedMarginStart().valueOr(0) + borderBox.width() + displayBox.computedMarginEnd().valueOr(0),
252 displayBox.nonCollapsedMarginBefore() + borderBox.height() + displayBox.nonCollapsedMarginAfter()
253 };
254 };
255
256 auto& displayBox = context.displayBoxForLayoutBox(layoutBox);
257
258 auto frameRect = renderer.frameRect();
259 // rendering does not offset for relative positioned boxes.
260 if (renderer.isInFlowPositioned())
261 frameRect.move(renderer.offsetForInFlowPosition());
262
263 if (frameRect != displayBox.rect()) {
264 outputRect("frameBox", renderer.frameRect(), displayBox.rect());
265 return true;
266 }
267
268 if (renderer.borderBoxRect() != displayBox.borderBox()) {
269 outputRect("borderBox", renderer.borderBoxRect(), displayBox.borderBox());
270 return true;
271 }
272
273 if (renderer.paddingBoxRect() != displayBox.paddingBox()) {
274 outputRect("paddingBox", renderer.paddingBoxRect(), displayBox.paddingBox());
275 return true;
276 }
277
278 if (renderer.contentBoxRect() != displayBox.contentBox()) {
279 outputRect("contentBox", renderer.contentBoxRect(), displayBox.contentBox());
280 return true;
281 }
282
283 if (renderer.marginBoxRect() != renderBoxLikeMarginBox(displayBox)) {
284 // In certain cases, like out-of-flow boxes with margin auto, marginBoxRect() returns 0. It's clearly incorrect,
285 // so let's check the individual margin values instead (and at this point we know that all other boxes match).
286 auto marginsMatch = displayBox.marginBefore() == renderer.marginBefore()
287 && displayBox.marginAfter() == renderer.marginAfter()
288 && displayBox.marginStart() == renderer.marginStart()
289 && displayBox.marginEnd() == renderer.marginEnd();
290
291 if (!marginsMatch) {
292 outputRect("marginBox", renderer.marginBoxRect(), renderBoxLikeMarginBox(displayBox));
293 return true;
294 }
295 }
296
297 return false;
298}
299
300static bool verifyAndOutputSubtree(TextStream& stream, const LayoutState& context, const RenderBox& renderer, const Box& layoutBox)
301{
302 auto mismtachingGeometry = outputMismatchingBlockBoxInformationIfNeeded(stream, context, renderer, layoutBox);
303
304 if (!is<Container>(layoutBox))
305 return mismtachingGeometry;
306
307 auto& container = downcast<Container>(layoutBox);
308 auto* childBox = container.firstChild();
309 auto* childRenderer = renderer.firstChild();
310
311 while (childRenderer) {
312 if (!is<RenderBox>(*childRenderer)) {
313 childRenderer = childRenderer->nextSibling();
314 continue;
315 }
316
317 if (!childBox) {
318 stream << "Trees are out of sync!";
319 stream.nextLine();
320 return true;
321 }
322
323 if (is<RenderBlockFlow>(*childRenderer) && childBox->establishesInlineFormattingContext()) {
324 ASSERT(childRenderer->childrenInline());
325 auto& blockFlow = downcast<RenderBlockFlow>(*childRenderer);
326 auto& formattingRoot = downcast<Container>(*childBox);
327 mismtachingGeometry |= blockFlow.lineLayoutPath() == RenderBlockFlow::SimpleLinesPath ? outputMismatchingSimpleLineInformationIfNeeded(stream, context, blockFlow, formattingRoot) : outputMismatchingComplexLineInformationIfNeeded(stream, context, blockFlow, formattingRoot);
328 } else {
329 auto mismatchingSubtreeGeometry = verifyAndOutputSubtree(stream, context, downcast<RenderBox>(*childRenderer), *childBox);
330 mismtachingGeometry |= mismatchingSubtreeGeometry;
331 }
332
333 childBox = childBox->nextSibling();
334 childRenderer = childRenderer->nextSibling();
335 }
336
337 return mismtachingGeometry;
338}
339
340void LayoutState::verifyAndOutputMismatchingLayoutTree(const RenderView& renderView) const
341{
342 TextStream stream;
343 auto mismatchingGeometry = verifyAndOutputSubtree(stream, *this, renderView, initialContainingBlock());
344 if (!mismatchingGeometry)
345 return;
346#if ENABLE(TREE_DEBUGGING)
347 showRenderTree(&renderView);
348 showLayoutTree(initialContainingBlock(), this);
349#endif
350 WTFLogAlways("%s", stream.release().utf8().data());
351 ASSERT_NOT_REACHED();
352}
353
354}
355}
356
357#endif
358