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 | |
38 | namespace WebCore { |
39 | namespace SimpleLineLayout { |
40 | |
41 | #ifndef NDEBUG |
42 | static 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 | |
207 | static 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 | |
219 | static 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 | |
237 | static 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 | |
249 | static 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 | |
269 | static 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 | |
280 | void toggleSimpleLineLayout() |
281 | { |
282 | for (auto* document : Document::allDocuments()) { |
283 | auto& settings = document->mutableSettings(); |
284 | settings.setSimpleLineLayoutEnabled(!settings.simpleLineLayoutEnabled()); |
285 | } |
286 | } |
287 | |
288 | void 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 | |
315 | void 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 | |