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 "SimpleLineLayoutCoverage.h"
28
29#include "Logging.h"
30#include "RenderBlockFlow.h"
31#include "RenderChildIterator.h"
32#include "RenderStyle.h"
33#include "RenderView.h"
34#include "Settings.h"
35#include "SimpleLineLayout.h"
36#include <wtf/text/TextStream.h>
37
38namespace WebCore {
39namespace SimpleLineLayout {
40
41#ifndef NDEBUG
42static void printReason(AvoidanceReason reason, TextStream& stream)
43{
44 switch (reason) {
45 case FlowIsInsideANonMultiColumnThread:
46 stream << "flow is inside a non-multicolumn container";
47 break;
48 case FlowHasHorizonalWritingMode:
49 stream << "horizontal writing mode";
50 break;
51 case FlowHasOutline:
52 stream << "outline";
53 break;
54 case FlowIsRuby:
55 stream << "ruby";
56 break;
57 case FlowHasHangingPunctuation:
58 stream << "hanging punctuation";
59 break;
60 case FlowIsPaginated:
61 stream << "paginated";
62 break;
63 case FlowHasTextOverflow:
64 stream << "text-overflow";
65 break;
66 case FlowIsDepricatedFlexBox:
67 stream << "depricatedFlexBox";
68 break;
69 case FlowParentIsPlaceholderElement:
70 stream << "placeholder element";
71 break;
72 case FlowParentIsTextAreaWithWrapping:
73 stream << "wrapping textarea";
74 break;
75 case FlowHasNonSupportedChild:
76 stream << "nested renderers";
77 break;
78 case FlowHasUnsupportedFloat:
79 stream << "complicated float";
80 break;
81 case FlowHasUnsupportedUnderlineDecoration:
82 stream << "text-underline-position: under";
83 break;
84 case FlowHasJustifiedNonLatinText:
85 stream << "text-align: justify with non-latin text";
86 break;
87 case FlowHasOverflowNotVisible:
88 stream << "overflow: hidden | scroll | auto";
89 break;
90 case FlowHasWebKitNBSPMode:
91 stream << "-webkit-nbsp-mode: space";
92 break;
93 case FlowIsNotLTR:
94 stream << "dir is not LTR";
95 break;
96 case FlowHasLineBoxContainProperty:
97 stream << "line-box-contain value indicates variable line height";
98 break;
99 case FlowIsNotTopToBottom:
100 stream << "non top-to-bottom flow";
101 break;
102 case FlowHasLineBreak:
103 stream << "line-break property";
104 break;
105 case FlowHasNonNormalUnicodeBiDi:
106 stream << "non-normal Unicode bidi";
107 break;
108 case FlowHasRTLOrdering:
109 stream << "-webkit-rtl-ordering";
110 break;
111 case FlowHasLineAlignEdges:
112 stream << "-webkit-line-align edges";
113 break;
114 case FlowHasLineSnap:
115 stream << "-webkit-line-snap property";
116 break;
117 case FlowHasTextEmphasisFillOrMark:
118 stream << "text-emphasis (fill/mark)";
119 break;
120 case FlowHasPseudoFirstLine:
121 stream << "first-line";
122 break;
123 case FlowHasPseudoFirstLetter:
124 stream << "first-letter";
125 break;
126 case FlowHasTextCombine:
127 stream << "text combine";
128 break;
129 case FlowHasTextFillBox:
130 stream << "background-color (text-fill)";
131 break;
132 case FlowHasBorderFitLines:
133 stream << "-webkit-border-fit";
134 break;
135 case FlowHasNonAutoLineBreak:
136 stream << "line-break is not auto";
137 break;
138 case FlowHasSVGFont:
139 stream << "SVG font";
140 break;
141 case FlowTextHasSoftHyphen:
142 stream << "soft hyphen character";
143 break;
144 case FlowTextHasDirectionCharacter:
145 stream << "direction character";
146 break;
147 case FlowIsMissingPrimaryFont:
148 stream << "missing primary font";
149 break;
150 case FlowPrimaryFontIsInsufficient:
151 stream << "missing glyph or glyph needs another font";
152 break;
153 case FlowTextIsCombineText:
154 stream << "text is combine";
155 break;
156 case FlowTextIsRenderCounter:
157 stream << "unsupported RenderCounter";
158 break;
159 case FlowTextIsRenderQuote:
160 stream << "unsupported RenderQuote";
161 break;
162 case FlowTextIsTextFragment:
163 stream << "unsupported TextFragment";
164 break;
165 case FlowTextIsSVGInlineText:
166 stream << "unsupported SVGInlineText";
167 break;
168 case FlowHasComplexFontCodePath:
169 stream << "text with complex font codepath";
170 break;
171 case FlowHasTextShadow:
172 stream << "text-shadow";
173 break;
174 case FlowChildIsSelected:
175 stream << "selected content";
176 break;
177 case FlowFontHasOverflowGlyph:
178 stream << "-webkit-line-box-contain: glyphs with overflowing text.";
179 break;
180 case FlowTextHasSurrogatePair:
181 stream << "surrogate pair";
182 break;
183 case MultiColumnFlowIsNotTopLevel:
184 stream << "non top level column";
185 break;
186 case MultiColumnFlowHasColumnSpanner:
187 stream << "column has spanner";
188 break;
189 case MultiColumnFlowVerticalAlign:
190 stream << "column with vertical-align != baseline";
191 break;
192 case MultiColumnFlowIsFloating:
193 stream << "column with floating objects";
194 break;
195 case FlowIncludesDocumentMarkers:
196 stream << "text includes document markers";
197 break;
198 case FlowTextIsEmpty:
199 case FlowHasNoChild:
200 case FlowHasNoParent:
201 case FeatureIsDisabled:
202 default:
203 break;
204 }
205}
206
207static void printReasons(AvoidanceReasonFlags reasons, TextStream& stream)
208{
209 bool first = true;
210 for (auto reasonItem = EndOfReasons >> 1; reasonItem != NoReason; reasonItem >>= 1) {
211 if (!(reasons & reasonItem))
212 continue;
213 stream << (first ? " " : ", ");
214 first = false;
215 printReason(reasonItem, stream);
216 }
217}
218
219static void printTextForSubtree(const RenderObject& renderer, unsigned& charactersLeft, TextStream& stream)
220{
221 if (!charactersLeft)
222 return;
223 if (is<RenderText>(renderer)) {
224 String text = downcast<RenderText>(renderer).text();
225 text = text.stripWhiteSpace();
226 unsigned len = std::min(charactersLeft, text.length());
227 stream << text.left(len);
228 charactersLeft -= len;
229 return;
230 }
231 if (!is<RenderElement>(renderer))
232 return;
233 for (const auto* child = downcast<RenderElement>(renderer).firstChild(); child; child = child->nextSibling())
234 printTextForSubtree(*child, charactersLeft, stream);
235}
236
237static unsigned textLengthForSubtree(const RenderObject& renderer)
238{
239 if (is<RenderText>(renderer))
240 return downcast<RenderText>(renderer).text().length();
241 if (!is<RenderElement>(renderer))
242 return 0;
243 unsigned textLength = 0;
244 for (const auto* child = downcast<RenderElement>(renderer).firstChild(); child; child = child->nextSibling())
245 textLength += textLengthForSubtree(*child);
246 return textLength;
247}
248
249static void collectNonEmptyLeafRenderBlockFlows(const RenderObject& renderer, HashSet<const RenderBlockFlow*>& leafRenderers)
250{
251 if (is<RenderText>(renderer)) {
252 if (!downcast<RenderText>(renderer).text().length())
253 return;
254 // Find RenderBlockFlow ancestor.
255 for (const auto* current = renderer.parent(); current; current = current->parent()) {
256 if (!is<RenderBlockFlow>(current))
257 continue;
258 leafRenderers.add(downcast<RenderBlockFlow>(current));
259 break;
260 }
261 return;
262 }
263 if (!is<RenderElement>(renderer))
264 return;
265 for (const auto* child = downcast<RenderElement>(renderer).firstChild(); child; child = child->nextSibling())
266 collectNonEmptyLeafRenderBlockFlows(*child, leafRenderers);
267}
268
269static void collectNonEmptyLeafRenderBlockFlowsForCurrentPage(HashSet<const RenderBlockFlow*>& leafRenderers)
270{
271 for (const auto* document : Document::allDocuments()) {
272 if (!document->renderView() || document->pageCacheState() != Document::NotInPageCache)
273 continue;
274 if (!document->isHTMLDocument() && !document->isXHTMLDocument())
275 continue;
276 collectNonEmptyLeafRenderBlockFlows(*document->renderView(), leafRenderers);
277 }
278}
279
280void toggleSimpleLineLayout()
281{
282 for (auto* document : Document::allDocuments()) {
283 auto& settings = document->mutableSettings();
284 settings.setSimpleLineLayoutEnabled(!settings.simpleLineLayoutEnabled());
285 }
286}
287
288void printSimpleLineLayoutBlockList()
289{
290 HashSet<const RenderBlockFlow*> leafRenderers;
291 collectNonEmptyLeafRenderBlockFlowsForCurrentPage(leafRenderers);
292 if (!leafRenderers.size()) {
293 WTFLogAlways("No text found in this document\n");
294 return;
295 }
296 TextStream stream;
297 stream << "---------------------------------------------------\n";
298 for (const auto* flow : leafRenderers) {
299 auto reason = canUseForWithReason(*flow, IncludeReasons::All);
300 if (reason == NoReason)
301 continue;
302 unsigned printedLength = 30;
303 stream << "\"";
304 printTextForSubtree(*flow, printedLength, stream);
305 for (;printedLength > 0; --printedLength)
306 stream << " ";
307 stream << "\"(" << textLengthForSubtree(*flow) << "):";
308 printReasons(reason, stream);
309 stream << "\n";
310 }
311 stream << "---------------------------------------------------\n";
312 WTFLogAlways("%s", stream.release().utf8().data());
313}
314
315void printSimpleLineLayoutCoverage()
316{
317 HashSet<const RenderBlockFlow*> leafRenderers;
318 collectNonEmptyLeafRenderBlockFlowsForCurrentPage(leafRenderers);
319 if (!leafRenderers.size()) {
320 WTFLogAlways("No text found in this document\n");
321 return;
322 }
323 TextStream stream;
324 HashMap<AvoidanceReason, unsigned> flowStatistics;
325 unsigned textLength = 0;
326 unsigned unsupportedTextLength = 0;
327 unsigned numberOfUnsupportedLeafBlocks = 0;
328 unsigned supportedButForcedToLineLayoutTextLength = 0;
329 unsigned numberOfSupportedButForcedToLineLayoutLeafBlocks = 0;
330 for (const auto* flow : leafRenderers) {
331 auto flowLength = textLengthForSubtree(*flow);
332 textLength += flowLength;
333 auto reasons = canUseForWithReason(*flow, IncludeReasons::All);
334 if (reasons == NoReason) {
335 if (flow->lineLayoutPath() == RenderBlockFlow::ForceLineBoxesPath) {
336 supportedButForcedToLineLayoutTextLength += flowLength;
337 ++numberOfSupportedButForcedToLineLayoutLeafBlocks;
338 }
339 continue;
340 }
341 ++numberOfUnsupportedLeafBlocks;
342 unsupportedTextLength += flowLength;
343 for (auto reasonItem = EndOfReasons >> 1; reasonItem != NoReason; reasonItem >>= 1) {
344 if (!(reasons & reasonItem))
345 continue;
346 auto result = flowStatistics.add(reasonItem, flowLength);
347 if (!result.isNewEntry)
348 result.iterator->value += flowLength;
349 }
350 }
351 stream << "---------------------------------------------------\n";
352 stream << "Number of blocks: total(" << leafRenderers.size() << ") non-simple(" << numberOfUnsupportedLeafBlocks << ")\nContent length: total(" <<
353 textLength << ") non-simple(" << unsupportedTextLength << ")\n";
354 for (const auto& reasonEntry : flowStatistics) {
355 printReason(reasonEntry.key, stream);
356 stream << ": " << (float)reasonEntry.value / (float)textLength * 100 << "%\n";
357 }
358 if (supportedButForcedToLineLayoutTextLength) {
359 stream << "Simple line layout potential coverage: " << (float)(textLength - unsupportedTextLength) / (float)textLength * 100 << "%\n\n";
360 stream << "Simple line layout actual coverage: " << (float)(textLength - unsupportedTextLength - supportedButForcedToLineLayoutTextLength) / (float)textLength * 100 << "%\nForced line layout blocks: " << numberOfSupportedButForcedToLineLayoutLeafBlocks << " content length: " << supportedButForcedToLineLayoutTextLength << "(" << (float)supportedButForcedToLineLayoutTextLength / (float)textLength * 100 << "%)";
361 } else
362 stream << "Simple line layout coverage: " << (float)(textLength - unsupportedTextLength) / (float)textLength * 100 << "%";
363 stream << "\n---------------------------------------------------\n";
364 WTFLogAlways("%s", stream.release().utf8().data());
365}
366#endif
367
368}
369}
370