1 | /* |
2 | * Copyright (C) 2004-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. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "RenderTreeAsText.h" |
28 | |
29 | #include "ClipRect.h" |
30 | #include "Document.h" |
31 | #include "Frame.h" |
32 | #include "FrameSelection.h" |
33 | #include "FrameView.h" |
34 | #include "HTMLElement.h" |
35 | #include "HTMLNames.h" |
36 | #include "HTMLSpanElement.h" |
37 | #include "InlineTextBox.h" |
38 | #include "Logging.h" |
39 | #include "PrintContext.h" |
40 | #include "PseudoElement.h" |
41 | #include "RenderBlockFlow.h" |
42 | #include "RenderCounter.h" |
43 | #include "RenderDetailsMarker.h" |
44 | #include "RenderFileUploadControl.h" |
45 | #include "RenderFragmentContainer.h" |
46 | #include "RenderInline.h" |
47 | #include "RenderIterator.h" |
48 | #include "RenderLayer.h" |
49 | #include "RenderLayerBacking.h" |
50 | #include "RenderLineBreak.h" |
51 | #include "RenderListItem.h" |
52 | #include "RenderListMarker.h" |
53 | #include "RenderSVGContainer.h" |
54 | #include "RenderSVGGradientStop.h" |
55 | #include "RenderSVGImage.h" |
56 | #include "RenderSVGInlineText.h" |
57 | #include "RenderSVGPath.h" |
58 | #include "RenderSVGResourceContainer.h" |
59 | #include "RenderSVGRoot.h" |
60 | #include "RenderSVGText.h" |
61 | #include "RenderTableCell.h" |
62 | #include "RenderView.h" |
63 | #include "RenderWidget.h" |
64 | #include "SVGRenderTreeAsText.h" |
65 | #include "ShadowRoot.h" |
66 | #include "SimpleLineLayoutResolver.h" |
67 | #include "StyleProperties.h" |
68 | #include <wtf/HexNumber.h> |
69 | #include <wtf/Vector.h> |
70 | #include <wtf/text/TextStream.h> |
71 | #include <wtf/unicode/CharacterNames.h> |
72 | |
73 | #if PLATFORM(MAC) |
74 | #include "ScrollbarThemeMac.h" |
75 | #endif |
76 | |
77 | namespace WebCore { |
78 | |
79 | using namespace HTMLNames; |
80 | |
81 | static void writeLayers(TextStream&, const RenderLayer& rootLayer, RenderLayer&, const LayoutRect& paintDirtyRect, OptionSet<RenderAsTextFlag>); |
82 | |
83 | static void printBorderStyle(TextStream& ts, const BorderStyle borderStyle) |
84 | { |
85 | switch (borderStyle) { |
86 | case BorderStyle::None: |
87 | ts << "none" ; |
88 | break; |
89 | case BorderStyle::Hidden: |
90 | ts << "hidden" ; |
91 | break; |
92 | case BorderStyle::Inset: |
93 | ts << "inset" ; |
94 | break; |
95 | case BorderStyle::Groove: |
96 | ts << "groove" ; |
97 | break; |
98 | case BorderStyle::Ridge: |
99 | ts << "ridge" ; |
100 | break; |
101 | case BorderStyle::Outset: |
102 | ts << "outset" ; |
103 | break; |
104 | case BorderStyle::Dotted: |
105 | ts << "dotted" ; |
106 | break; |
107 | case BorderStyle::Dashed: |
108 | ts << "dashed" ; |
109 | break; |
110 | case BorderStyle::Solid: |
111 | ts << "solid" ; |
112 | break; |
113 | case BorderStyle::Double: |
114 | ts << "double" ; |
115 | break; |
116 | } |
117 | |
118 | ts << " " ; |
119 | } |
120 | |
121 | static String getTagName(Node* n) |
122 | { |
123 | if (n->isDocumentNode()) |
124 | return "" ; |
125 | if (n->nodeType() == Node::COMMENT_NODE) |
126 | return "COMMENT" ; |
127 | return n->nodeName(); |
128 | } |
129 | |
130 | static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node) |
131 | { |
132 | if (!is<HTMLSpanElement>(node)) |
133 | return false; |
134 | |
135 | const HTMLElement& element = downcast<HTMLSpanElement>(*node); |
136 | if (element.getAttribute(classAttr) != "Apple-style-span" ) |
137 | return false; |
138 | |
139 | if (!node->hasChildNodes()) |
140 | return true; |
141 | |
142 | const StyleProperties* inlineStyleDecl = element.inlineStyle(); |
143 | return (!inlineStyleDecl || inlineStyleDecl->isEmpty()); |
144 | } |
145 | |
146 | String quoteAndEscapeNonPrintables(StringView s) |
147 | { |
148 | StringBuilder result; |
149 | result.append('"'); |
150 | for (unsigned i = 0; i != s.length(); ++i) { |
151 | UChar c = s[i]; |
152 | if (c == '\\') { |
153 | result.appendLiteral("\\\\" ); |
154 | } else if (c == '"') { |
155 | result.appendLiteral("\\\"" ); |
156 | } else if (c == '\n' || c == noBreakSpace) |
157 | result.append(' '); |
158 | else { |
159 | if (c >= 0x20 && c < 0x7F) |
160 | result.append(c); |
161 | else { |
162 | result.appendLiteral("\\x{" ); |
163 | appendUnsignedAsHex(c, result); |
164 | result.append('}'); |
165 | } |
166 | } |
167 | } |
168 | result.append('"'); |
169 | return result.toString(); |
170 | } |
171 | |
172 | void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, OptionSet<RenderAsTextFlag> behavior) |
173 | { |
174 | ts << o.renderName(); |
175 | |
176 | if (behavior.contains(RenderAsTextFlag::ShowAddresses)) |
177 | ts << " " << static_cast<const void*>(&o); |
178 | |
179 | if (o.style().zIndex()) |
180 | ts << " zI: " << o.style().zIndex(); |
181 | |
182 | if (o.node()) { |
183 | String tagName = getTagName(o.node()); |
184 | // FIXME: Temporary hack to make tests pass by simulating the old generated content output. |
185 | if (o.isPseudoElement() || (o.parent() && o.parent()->isPseudoElement())) |
186 | tagName = emptyAtom(); |
187 | if (!tagName.isEmpty()) { |
188 | ts << " {" << tagName << "}" ; |
189 | // flag empty or unstyled AppleStyleSpan because we never |
190 | // want to leave them in the DOM |
191 | if (isEmptyOrUnstyledAppleStyleSpan(o.node())) |
192 | ts << " *empty or unstyled AppleStyleSpan*" ; |
193 | } |
194 | } |
195 | |
196 | RenderBlock* cb = o.containingBlock(); |
197 | bool adjustForTableCells = cb ? cb->isTableCell() : false; |
198 | |
199 | LayoutRect r; |
200 | if (is<RenderText>(o)) { |
201 | // FIXME: Would be better to dump the bounding box x and y rather than the first run's x and y, but that would involve updating |
202 | // many test results. |
203 | const RenderText& text = downcast<RenderText>(o); |
204 | r = IntRect(text.firstRunLocation(), text.linesBoundingBox().size()); |
205 | if (!text.firstTextBox() && !text.simpleLineLayout()) |
206 | adjustForTableCells = false; |
207 | } else if (o.isBR()) { |
208 | const RenderLineBreak& br = downcast<RenderLineBreak>(o); |
209 | IntRect linesBox = br.linesBoundingBox(); |
210 | r = IntRect(linesBox.x(), linesBox.y(), linesBox.width(), linesBox.height()); |
211 | if (!br.inlineBoxWrapper()) |
212 | adjustForTableCells = false; |
213 | } else if (is<RenderInline>(o)) { |
214 | const RenderInline& inlineFlow = downcast<RenderInline>(o); |
215 | // FIXME: Would be better not to just dump 0, 0 as the x and y here. |
216 | r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height()); |
217 | adjustForTableCells = false; |
218 | } else if (is<RenderTableCell>(o)) { |
219 | // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like |
220 | // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are |
221 | // captured by the results. |
222 | const RenderTableCell& cell = downcast<RenderTableCell>(o); |
223 | r = LayoutRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter()); |
224 | } else if (is<RenderBox>(o)) |
225 | r = downcast<RenderBox>(o).frameRect(); |
226 | |
227 | // FIXME: Temporary in order to ensure compatibility with existing layout test results. |
228 | if (adjustForTableCells) |
229 | r.move(0_lu, -downcast<RenderTableCell>(*o.containingBlock()).intrinsicPaddingBefore()); |
230 | |
231 | // FIXME: Convert layout test results to report sub-pixel values, in the meantime using enclosingIntRect |
232 | // for consistency with old results. |
233 | ts << " " << enclosingIntRect(r); |
234 | |
235 | if (!is<RenderText>(o)) { |
236 | if (is<RenderFileUploadControl>(o)) |
237 | ts << " " << quoteAndEscapeNonPrintables(downcast<RenderFileUploadControl>(o).fileTextValue()); |
238 | |
239 | if (o.parent()) { |
240 | Color color = o.style().visitedDependentColor(CSSPropertyColor); |
241 | if (o.parent()->style().visitedDependentColor(CSSPropertyColor).rgb() != color.rgb()) |
242 | ts << " [color=" << color.nameForRenderTreeAsText() << "]" ; |
243 | |
244 | // Do not dump invalid or transparent backgrounds, since that is the default. |
245 | Color backgroundColor = o.style().visitedDependentColor(CSSPropertyBackgroundColor); |
246 | if (o.parent()->style().visitedDependentColor(CSSPropertyBackgroundColor).rgb() != backgroundColor.rgb() |
247 | && backgroundColor.isValid() && backgroundColor.rgb()) |
248 | ts << " [bgcolor=" << backgroundColor.nameForRenderTreeAsText() << "]" ; |
249 | |
250 | Color textFillColor = o.style().visitedDependentColor(CSSPropertyWebkitTextFillColor); |
251 | if (o.parent()->style().visitedDependentColor(CSSPropertyWebkitTextFillColor).rgb() != textFillColor.rgb() |
252 | && textFillColor.isValid() && textFillColor.rgb() != color.rgb() && textFillColor.rgb()) |
253 | ts << " [textFillColor=" << textFillColor.nameForRenderTreeAsText() << "]" ; |
254 | |
255 | Color textStrokeColor = o.style().visitedDependentColor(CSSPropertyWebkitTextStrokeColor); |
256 | if (o.parent()->style().visitedDependentColor(CSSPropertyWebkitTextStrokeColor).rgb() != textStrokeColor.rgb() |
257 | && textStrokeColor.isValid() && textStrokeColor.rgb() != color.rgb() && textStrokeColor.rgb()) |
258 | ts << " [textStrokeColor=" << textStrokeColor.nameForRenderTreeAsText() << "]" ; |
259 | |
260 | if (o.parent()->style().textStrokeWidth() != o.style().textStrokeWidth() && o.style().textStrokeWidth() > 0) |
261 | ts << " [textStrokeWidth=" << o.style().textStrokeWidth() << "]" ; |
262 | } |
263 | |
264 | if (!is<RenderBoxModelObject>(o) || is<RenderLineBreak>(o)) |
265 | return; |
266 | |
267 | const RenderBoxModelObject& box = downcast<RenderBoxModelObject>(o); |
268 | LayoutUnit borderTop = box.borderTop(); |
269 | LayoutUnit borderRight = box.borderRight(); |
270 | LayoutUnit borderBottom = box.borderBottom(); |
271 | LayoutUnit borderLeft = box.borderLeft(); |
272 | if (box.isFieldset()) { |
273 | const auto& block = downcast<RenderBlock>(box); |
274 | if (o.style().writingMode() == TopToBottomWritingMode) |
275 | borderTop -= block.intrinsicBorderForFieldset(); |
276 | else if (o.style().writingMode() == BottomToTopWritingMode) |
277 | borderBottom -= block.intrinsicBorderForFieldset(); |
278 | else if (o.style().writingMode() == LeftToRightWritingMode) |
279 | borderLeft -= block.intrinsicBorderForFieldset(); |
280 | else if (o.style().writingMode() == RightToLeftWritingMode) |
281 | borderRight -= block.intrinsicBorderForFieldset(); |
282 | |
283 | } |
284 | if (borderTop || borderRight || borderBottom || borderLeft) { |
285 | ts << " [border:" ; |
286 | |
287 | BorderValue prevBorder = o.style().borderTop(); |
288 | if (!borderTop) |
289 | ts << " none" ; |
290 | else { |
291 | ts << " (" << borderTop << "px " ; |
292 | printBorderStyle(ts, o.style().borderTopStyle()); |
293 | Color col = o.style().borderTopColor(); |
294 | if (!col.isValid()) |
295 | col = o.style().color(); |
296 | ts << col.nameForRenderTreeAsText() << ")" ; |
297 | } |
298 | |
299 | if (o.style().borderRight() != prevBorder) { |
300 | prevBorder = o.style().borderRight(); |
301 | if (!borderRight) |
302 | ts << " none" ; |
303 | else { |
304 | ts << " (" << borderRight << "px " ; |
305 | printBorderStyle(ts, o.style().borderRightStyle()); |
306 | Color col = o.style().borderRightColor(); |
307 | if (!col.isValid()) |
308 | col = o.style().color(); |
309 | ts << col.nameForRenderTreeAsText() << ")" ; |
310 | } |
311 | } |
312 | |
313 | if (o.style().borderBottom() != prevBorder) { |
314 | prevBorder = box.style().borderBottom(); |
315 | if (!borderBottom) |
316 | ts << " none" ; |
317 | else { |
318 | ts << " (" << borderBottom << "px " ; |
319 | printBorderStyle(ts, o.style().borderBottomStyle()); |
320 | Color col = o.style().borderBottomColor(); |
321 | if (!col.isValid()) |
322 | col = o.style().color(); |
323 | ts << col.nameForRenderTreeAsText() << ")" ; |
324 | } |
325 | } |
326 | |
327 | if (o.style().borderLeft() != prevBorder) { |
328 | prevBorder = o.style().borderLeft(); |
329 | if (!borderLeft) |
330 | ts << " none" ; |
331 | else { |
332 | ts << " (" << borderLeft << "px " ; |
333 | printBorderStyle(ts, o.style().borderLeftStyle()); |
334 | Color col = o.style().borderLeftColor(); |
335 | if (!col.isValid()) |
336 | col = o.style().color(); |
337 | ts << col.nameForRenderTreeAsText() << ")" ; |
338 | } |
339 | } |
340 | |
341 | ts << "]" ; |
342 | } |
343 | |
344 | #if ENABLE(MATHML) |
345 | // We want to show any layout padding, both CSS padding and intrinsic padding, so we can't just check o.style().hasPadding(). |
346 | if (o.isRenderMathMLBlock() && (box.paddingTop() || box.paddingRight() || box.paddingBottom() || box.paddingLeft())) { |
347 | ts << " [" ; |
348 | LayoutUnit cssTop = box.computedCSSPaddingTop(); |
349 | LayoutUnit cssRight = box.computedCSSPaddingRight(); |
350 | LayoutUnit cssBottom = box.computedCSSPaddingBottom(); |
351 | LayoutUnit cssLeft = box.computedCSSPaddingLeft(); |
352 | if (box.paddingTop() != cssTop || box.paddingRight() != cssRight || box.paddingBottom() != cssBottom || box.paddingLeft() != cssLeft) { |
353 | ts << "intrinsic " ; |
354 | if (cssTop || cssRight || cssBottom || cssLeft) |
355 | ts << "+ CSS " ; |
356 | } |
357 | ts << "padding: " << roundToInt(box.paddingTop()) << " " << roundToInt(box.paddingRight()) << " " << roundToInt(box.paddingBottom()) << " " << roundToInt(box.paddingLeft()) << "]" ; |
358 | } |
359 | #endif |
360 | } |
361 | |
362 | if (is<RenderTableCell>(o)) { |
363 | const RenderTableCell& c = downcast<RenderTableCell>(o); |
364 | ts << " [r=" << c.rowIndex() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]" ; |
365 | } |
366 | |
367 | if (is<RenderDetailsMarker>(o)) { |
368 | ts << ": " ; |
369 | switch (downcast<RenderDetailsMarker>(o).orientation()) { |
370 | case RenderDetailsMarker::Left: |
371 | ts << "left" ; |
372 | break; |
373 | case RenderDetailsMarker::Right: |
374 | ts << "right" ; |
375 | break; |
376 | case RenderDetailsMarker::Up: |
377 | ts << "up" ; |
378 | break; |
379 | case RenderDetailsMarker::Down: |
380 | ts << "down" ; |
381 | break; |
382 | } |
383 | } |
384 | |
385 | if (is<RenderListMarker>(o)) { |
386 | String text = downcast<RenderListMarker>(o).text(); |
387 | if (!text.isEmpty()) { |
388 | if (text.length() != 1) |
389 | text = quoteAndEscapeNonPrintables(text); |
390 | else { |
391 | switch (text[0]) { |
392 | case bullet: |
393 | text = "bullet" ; |
394 | break; |
395 | case blackSquare: |
396 | text = "black square" ; |
397 | break; |
398 | case whiteBullet: |
399 | text = "white bullet" ; |
400 | break; |
401 | default: |
402 | text = quoteAndEscapeNonPrintables(text); |
403 | } |
404 | } |
405 | ts << ": " << text; |
406 | } |
407 | } |
408 | |
409 | writeDebugInfo(ts, o, behavior); |
410 | } |
411 | |
412 | void writeDebugInfo(TextStream& ts, const RenderObject& object, OptionSet<RenderAsTextFlag> behavior) |
413 | { |
414 | if (behavior.contains(RenderAsTextFlag::ShowIDAndClass)) { |
415 | if (Element* element = is<Element>(object.node()) ? downcast<Element>(object.node()) : nullptr) { |
416 | if (element->hasID()) |
417 | ts << " id=\"" + element->getIdAttribute() + "\"" ; |
418 | |
419 | if (element->hasClass()) { |
420 | ts << " class=\"" ; |
421 | for (size_t i = 0; i < element->classNames().size(); ++i) { |
422 | if (i > 0) |
423 | ts << " " ; |
424 | ts << element->classNames()[i]; |
425 | } |
426 | ts << "\"" ; |
427 | } |
428 | } |
429 | } |
430 | |
431 | if (behavior.contains(RenderAsTextFlag::ShowLayoutState)) { |
432 | bool needsLayout = object.selfNeedsLayout() || object.needsPositionedMovementLayout() || object.posChildNeedsLayout() || object.normalChildNeedsLayout(); |
433 | if (needsLayout) |
434 | ts << " (needs layout:" ; |
435 | |
436 | bool havePrevious = false; |
437 | if (object.selfNeedsLayout()) { |
438 | ts << " self" ; |
439 | havePrevious = true; |
440 | } |
441 | |
442 | if (object.needsPositionedMovementLayout()) { |
443 | if (havePrevious) |
444 | ts << "," ; |
445 | havePrevious = true; |
446 | ts << " positioned movement" ; |
447 | } |
448 | |
449 | if (object.normalChildNeedsLayout()) { |
450 | if (havePrevious) |
451 | ts << "," ; |
452 | havePrevious = true; |
453 | ts << " child" ; |
454 | } |
455 | |
456 | if (object.posChildNeedsLayout()) { |
457 | if (havePrevious) |
458 | ts << "," ; |
459 | ts << " positioned child" ; |
460 | } |
461 | |
462 | if (needsLayout) |
463 | ts << ")" ; |
464 | } |
465 | |
466 | if (behavior.contains(RenderAsTextFlag::ShowOverflow) && is<RenderBox>(object)) { |
467 | const auto& box = downcast<RenderBox>(object); |
468 | if (box.hasRenderOverflow()) { |
469 | LayoutRect layoutOverflow = box.layoutOverflowRect(); |
470 | ts << " (layout overflow " << layoutOverflow.x().toInt() << "," << layoutOverflow.y().toInt() << " " << layoutOverflow.width().toInt() << "x" << layoutOverflow.height().toInt() << ")" ; |
471 | |
472 | if (box.hasVisualOverflow()) { |
473 | LayoutRect visualOverflow = box.visualOverflowRect(); |
474 | ts << " (visual overflow " << visualOverflow.x().toInt() << "," << visualOverflow.y().toInt() << " " << visualOverflow.width().toInt() << "x" << visualOverflow.height().toInt() << ")" ; |
475 | } |
476 | } |
477 | } |
478 | } |
479 | |
480 | static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) |
481 | { |
482 | // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder |
483 | // to detect any changes caused by the conversion to floating point. :( |
484 | int x = run.x(); |
485 | int y = run.y(); |
486 | int logicalWidth = ceilf(run.left() + run.logicalWidth()) - x; |
487 | |
488 | // FIXME: Table cell adjustment is temporary until results can be updated. |
489 | if (is<RenderTableCell>(*o.containingBlock())) |
490 | y -= floorToInt(downcast<RenderTableCell>(*o.containingBlock()).intrinsicPaddingBefore()); |
491 | |
492 | ts << "text run at (" << x << "," << y << ") width " << logicalWidth; |
493 | if (!run.isLeftToRightDirection() || run.dirOverride()) { |
494 | ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR" ); |
495 | if (run.dirOverride()) |
496 | ts << " override" ; |
497 | } |
498 | ts << ": " |
499 | << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len())); |
500 | if (run.hasHyphen()) |
501 | ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style().hyphenString().string()); |
502 | ts << "\n" ; |
503 | } |
504 | |
505 | static void writeSimpleLine(TextStream& ts, const RenderText& renderText, const SimpleLineLayout::RunResolver::Run& run) |
506 | { |
507 | auto rect = run.rect(); |
508 | int x = rect.x(); |
509 | int y = rect.y(); |
510 | int logicalWidth = ceilf(rect.x() + rect.width()) - x; |
511 | |
512 | if (is<RenderTableCell>(*renderText.containingBlock())) |
513 | y -= floorToInt(downcast<RenderTableCell>(*renderText.containingBlock()).intrinsicPaddingBefore()); |
514 | |
515 | ts << "text run at (" << x << "," << y << ") width " << logicalWidth; |
516 | ts << ": " << quoteAndEscapeNonPrintables(run.text()); |
517 | if (run.hasHyphen()) |
518 | ts << " + hyphen string " << quoteAndEscapeNonPrintables(renderText.style().hyphenString().string()); |
519 | ts << "\n" ; |
520 | } |
521 | |
522 | void write(TextStream& ts, const RenderObject& o, OptionSet<RenderAsTextFlag> behavior) |
523 | { |
524 | if (is<RenderSVGShape>(o)) { |
525 | write(ts, downcast<RenderSVGShape>(o), behavior); |
526 | return; |
527 | } |
528 | if (is<RenderSVGGradientStop>(o)) { |
529 | writeSVGGradientStop(ts, downcast<RenderSVGGradientStop>(o), behavior); |
530 | return; |
531 | } |
532 | if (is<RenderSVGResourceContainer>(o)) { |
533 | writeSVGResourceContainer(ts, downcast<RenderSVGResourceContainer>(o), behavior); |
534 | return; |
535 | } |
536 | if (is<RenderSVGContainer>(o)) { |
537 | writeSVGContainer(ts, downcast<RenderSVGContainer>(o), behavior); |
538 | return; |
539 | } |
540 | if (is<RenderSVGRoot>(o)) { |
541 | write(ts, downcast<RenderSVGRoot>(o), behavior); |
542 | return; |
543 | } |
544 | if (is<RenderSVGText>(o)) { |
545 | writeSVGText(ts, downcast<RenderSVGText>(o), behavior); |
546 | return; |
547 | } |
548 | if (is<RenderSVGInlineText>(o)) { |
549 | writeSVGInlineText(ts, downcast<RenderSVGInlineText>(o), behavior); |
550 | return; |
551 | } |
552 | if (is<RenderSVGImage>(o)) { |
553 | writeSVGImage(ts, downcast<RenderSVGImage>(o), behavior); |
554 | return; |
555 | } |
556 | |
557 | ts << indent; |
558 | |
559 | RenderTreeAsText::writeRenderObject(ts, o, behavior); |
560 | ts << "\n" ; |
561 | |
562 | TextStream::IndentScope indentScope(ts); |
563 | |
564 | if (is<RenderText>(o)) { |
565 | auto& text = downcast<RenderText>(o); |
566 | if (auto layout = text.simpleLineLayout()) { |
567 | ASSERT(!text.firstTextBox()); |
568 | auto resolver = runResolver(downcast<RenderBlockFlow>(*text.parent()), *layout); |
569 | for (auto run : resolver.rangeForRenderer(text)) { |
570 | ts << indent; |
571 | writeSimpleLine(ts, text, run); |
572 | } |
573 | } else { |
574 | for (auto* box = text.firstTextBox(); box; box = box->nextTextBox()) { |
575 | ts << indent; |
576 | writeTextRun(ts, text, *box); |
577 | } |
578 | } |
579 | |
580 | } else { |
581 | for (auto& child : childrenOfType<RenderObject>(downcast<RenderElement>(o))) { |
582 | if (child.hasLayer()) |
583 | continue; |
584 | write(ts, child, behavior); |
585 | } |
586 | } |
587 | |
588 | if (is<RenderWidget>(o)) { |
589 | Widget* widget = downcast<RenderWidget>(o).widget(); |
590 | if (is<FrameView>(widget)) { |
591 | FrameView& view = downcast<FrameView>(*widget); |
592 | if (RenderView* root = view.frame().contentRenderer()) { |
593 | if (!(behavior.contains(RenderAsTextFlag::DontUpdateLayout))) |
594 | view.layoutContext().layout(); |
595 | if (RenderLayer* layer = root->layer()) |
596 | writeLayers(ts, *layer, *layer, layer->rect(), behavior); |
597 | } |
598 | } |
599 | } |
600 | } |
601 | |
602 | enum LayerPaintPhase { |
603 | LayerPaintPhaseAll = 0, |
604 | LayerPaintPhaseBackground = -1, |
605 | LayerPaintPhaseForeground = 1 |
606 | }; |
607 | |
608 | static void writeLayer(TextStream& ts, const RenderLayer& layer, const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, const LayoutRect& clipRect, |
609 | LayerPaintPhase paintPhase = LayerPaintPhaseAll, OptionSet<RenderAsTextFlag> behavior = { }) |
610 | { |
611 | IntRect adjustedLayoutBounds = snappedIntRect(layerBounds); |
612 | IntRect adjustedBackgroundClipRect = snappedIntRect(backgroundClipRect); |
613 | IntRect adjustedClipRect = snappedIntRect(clipRect); |
614 | |
615 | ts << indent << "layer " ; |
616 | |
617 | if (behavior.contains(RenderAsTextFlag::ShowAddresses)) |
618 | ts << static_cast<const void*>(&layer) << " " ; |
619 | |
620 | ts << adjustedLayoutBounds; |
621 | |
622 | if (!adjustedLayoutBounds.isEmpty()) { |
623 | if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds)) |
624 | ts << " backgroundClip " << adjustedBackgroundClipRect; |
625 | if (!adjustedClipRect.contains(adjustedLayoutBounds)) |
626 | ts << " clip " << adjustedClipRect; |
627 | } |
628 | |
629 | if (layer.renderer().hasOverflowClip()) { |
630 | if (layer.scrollOffset().x()) |
631 | ts << " scrollX " << layer.scrollOffset().x(); |
632 | if (layer.scrollOffset().y()) |
633 | ts << " scrollY " << layer.scrollOffset().y(); |
634 | if (layer.renderBox() && roundToInt(layer.renderBox()->clientWidth()) != layer.scrollWidth()) |
635 | ts << " scrollWidth " << layer.scrollWidth(); |
636 | if (layer.renderBox() && roundToInt(layer.renderBox()->clientHeight()) != layer.scrollHeight()) |
637 | ts << " scrollHeight " << layer.scrollHeight(); |
638 | #if PLATFORM(MAC) |
639 | ScrollbarTheme& scrollbarTheme = ScrollbarTheme::theme(); |
640 | if (!scrollbarTheme.isMockTheme() && layer.hasVerticalScrollbar()) { |
641 | ScrollbarThemeMac& macTheme = *static_cast<ScrollbarThemeMac*>(&scrollbarTheme); |
642 | if (macTheme.isLayoutDirectionRTL(*layer.verticalScrollbar())) |
643 | ts << " scrollbarHasRTLLayoutDirection" ; |
644 | } |
645 | #endif |
646 | } |
647 | |
648 | if (paintPhase == LayerPaintPhaseBackground) |
649 | ts << " layerType: background only" ; |
650 | else if (paintPhase == LayerPaintPhaseForeground) |
651 | ts << " layerType: foreground only" ; |
652 | |
653 | if (behavior.contains(RenderAsTextFlag::ShowCompositedLayers)) { |
654 | if (layer.isComposited()) { |
655 | ts << " (composited, bounds=" << layer.backing()->compositedBounds() << ", drawsContent=" << layer.backing()->graphicsLayer()->drawsContent() |
656 | << ", paints into ancestor=" << layer.backing()->paintsIntoCompositedAncestor() << ")" ; |
657 | } else if (layer.paintsIntoProvidedBacking()) |
658 | ts << " (shared backing of " << layer.backingProviderLayer() << ")" ; |
659 | } |
660 | |
661 | #if ENABLE(CSS_COMPOSITING) |
662 | if (layer.isolatesBlending()) |
663 | ts << " isolatesBlending" ; |
664 | if (layer.hasBlendMode()) |
665 | ts << " blendMode: " << compositeOperatorName(CompositeSourceOver, layer.blendMode()); |
666 | #endif |
667 | |
668 | ts << "\n" ; |
669 | } |
670 | |
671 | static void writeLayerRenderers(TextStream& ts, const RenderLayer& layer, LayerPaintPhase paintPhase, OptionSet<RenderAsTextFlag> behavior) |
672 | { |
673 | if (paintPhase != LayerPaintPhaseBackground) { |
674 | TextStream::IndentScope indentScope(ts); |
675 | write(ts, layer.renderer(), behavior); |
676 | } |
677 | } |
678 | |
679 | static LayoutSize maxLayoutOverflow(const RenderBox* box) |
680 | { |
681 | LayoutRect overflowRect = box->layoutOverflowRect(); |
682 | return LayoutSize(overflowRect.maxX(), overflowRect.maxY()); |
683 | } |
684 | |
685 | static void writeLayers(TextStream& ts, const RenderLayer& rootLayer, RenderLayer& layer, const LayoutRect& paintRect, OptionSet<RenderAsTextFlag> behavior) |
686 | { |
687 | // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh. |
688 | LayoutRect paintDirtyRect(paintRect); |
689 | if (&rootLayer == &layer) { |
690 | paintDirtyRect.setWidth(std::max<LayoutUnit>(paintDirtyRect.width(), rootLayer.renderBox()->layoutOverflowRect().maxX())); |
691 | paintDirtyRect.setHeight(std::max<LayoutUnit>(paintDirtyRect.height(), rootLayer.renderBox()->layoutOverflowRect().maxY())); |
692 | layer.setSize(layer.size().expandedTo(snappedIntSize(maxLayoutOverflow(layer.renderBox()), LayoutPoint(0, 0)))); |
693 | } |
694 | |
695 | // Calculate the clip rects we should use. |
696 | LayoutRect layerBounds; |
697 | ClipRect damageRect; |
698 | ClipRect clipRectToApply; |
699 | LayoutSize offsetFromRoot = layer.offsetFromAncestor(&rootLayer); |
700 | layer.calculateRects(RenderLayer::ClipRectsContext(&rootLayer, TemporaryClipRects), paintDirtyRect, layerBounds, damageRect, clipRectToApply, offsetFromRoot); |
701 | |
702 | // Ensure our lists are up-to-date. |
703 | layer.updateLayerListsIfNeeded(); |
704 | layer.updateDescendantDependentFlags(); |
705 | |
706 | bool shouldPaint = (behavior.contains(RenderAsTextFlag::ShowAllLayers)) ? true : layer.intersectsDamageRect(layerBounds, damageRect.rect(), &rootLayer, layer.offsetFromAncestor(&rootLayer)); |
707 | auto negativeZOrderLayers = layer.negativeZOrderLayers(); |
708 | bool paintsBackgroundSeparately = negativeZOrderLayers.size() > 0; |
709 | if (shouldPaint && paintsBackgroundSeparately) { |
710 | writeLayer(ts, layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), LayerPaintPhaseBackground, behavior); |
711 | writeLayerRenderers(ts, layer, LayerPaintPhaseBackground, behavior); |
712 | } |
713 | |
714 | if (negativeZOrderLayers.size()) { |
715 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) { |
716 | ts << indent << " negative z-order list (" << negativeZOrderLayers.size() << ")\n" ; |
717 | ts.increaseIndent(); |
718 | } |
719 | |
720 | for (auto* currLayer : negativeZOrderLayers) |
721 | writeLayers(ts, rootLayer, *currLayer, paintDirtyRect, behavior); |
722 | |
723 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) |
724 | ts.decreaseIndent(); |
725 | } |
726 | |
727 | if (shouldPaint) { |
728 | writeLayer(ts, layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, behavior); |
729 | |
730 | if (behavior.contains(RenderAsTextFlag::ShowLayerFragments)) { |
731 | LayerFragments layerFragments; |
732 | layer.collectFragments(layerFragments, &rootLayer, paintDirtyRect, RenderLayer::PaginationInclusionMode::ExcludeCompositedPaginatedLayers, TemporaryClipRects, IgnoreOverlayScrollbarSize, RespectOverflowClip, offsetFromRoot); |
733 | |
734 | if (layerFragments.size() > 1) { |
735 | TextStream::IndentScope indentScope(ts, 2); |
736 | for (unsigned i = 0; i < layerFragments.size(); ++i) { |
737 | const auto& fragment = layerFragments[i]; |
738 | ts << indent << " fragment " << i << ": bounds in layer " << fragment.layerBounds << " fragment bounds " << fragment.boundingBox << "\n" ; |
739 | } |
740 | } |
741 | } |
742 | |
743 | writeLayerRenderers(ts, layer, paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, behavior); |
744 | } |
745 | |
746 | auto normalFlowLayers = layer.normalFlowLayers(); |
747 | if (normalFlowLayers.size()) { |
748 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) { |
749 | ts << indent << " normal flow list (" << normalFlowLayers.size() << ")\n" ; |
750 | ts.increaseIndent(); |
751 | } |
752 | |
753 | for (auto* currLayer : normalFlowLayers) |
754 | writeLayers(ts, rootLayer, *currLayer, paintDirtyRect, behavior); |
755 | |
756 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) |
757 | ts.decreaseIndent(); |
758 | } |
759 | |
760 | auto positiveZOrderLayers = layer.positiveZOrderLayers(); |
761 | if (positiveZOrderLayers.size()) { |
762 | size_t layerCount = positiveZOrderLayers.size(); |
763 | |
764 | if (layerCount) { |
765 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) { |
766 | ts << indent << " positive z-order list (" << layerCount << ")\n" ; |
767 | ts.increaseIndent(); |
768 | } |
769 | |
770 | for (auto* currLayer : positiveZOrderLayers) |
771 | writeLayers(ts, rootLayer, *currLayer, paintDirtyRect, behavior); |
772 | |
773 | if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) |
774 | ts.decreaseIndent(); |
775 | } |
776 | } |
777 | } |
778 | |
779 | static String nodePosition(Node* node) |
780 | { |
781 | StringBuilder result; |
782 | |
783 | auto* body = node->document().bodyOrFrameset(); |
784 | Node* parent; |
785 | for (Node* n = node; n; n = parent) { |
786 | parent = n->parentOrShadowHostNode(); |
787 | if (n != node) |
788 | result.appendLiteral(" of " ); |
789 | if (parent) { |
790 | if (body && n == body) { |
791 | // We don't care what offset body may be in the document. |
792 | result.appendLiteral("body" ); |
793 | break; |
794 | } |
795 | if (n->isShadowRoot()) { |
796 | result.append('{'); |
797 | result.append(getTagName(n)); |
798 | result.append('}'); |
799 | } else { |
800 | result.appendLiteral("child " ); |
801 | result.appendNumber(n->computeNodeIndex()); |
802 | result.appendLiteral(" {" ); |
803 | result.append(getTagName(n)); |
804 | result.append('}'); |
805 | } |
806 | } else |
807 | result.appendLiteral("document" ); |
808 | } |
809 | |
810 | return result.toString(); |
811 | } |
812 | |
813 | static void writeSelection(TextStream& ts, const RenderBox& renderer) |
814 | { |
815 | if (!renderer.isRenderView()) |
816 | return; |
817 | |
818 | Frame* frame = renderer.document().frame(); |
819 | if (!frame) |
820 | return; |
821 | |
822 | VisibleSelection selection = frame->selection().selection(); |
823 | if (selection.isCaret()) { |
824 | ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()); |
825 | if (selection.affinity() == UPSTREAM) |
826 | ts << " (upstream affinity)" ; |
827 | ts << "\n" ; |
828 | } else if (selection.isRange()) |
829 | ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()) << "\n" |
830 | << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().deprecatedNode()) << "\n" ; |
831 | } |
832 | |
833 | static String externalRepresentation(RenderBox& renderer, OptionSet<RenderAsTextFlag> behavior) |
834 | { |
835 | TextStream ts(TextStream::LineMode::MultipleLine, TextStream::Formatting::SVGStyleRect | TextStream::Formatting::LayoutUnitsAsIntegers); |
836 | if (!renderer.hasLayer()) |
837 | return ts.release(); |
838 | |
839 | LOG(Layout, "externalRepresentation: dumping layer tree" ); |
840 | |
841 | RenderLayer& layer = *renderer.layer(); |
842 | writeLayers(ts, layer, layer, layer.rect(), behavior); |
843 | writeSelection(ts, renderer); |
844 | return ts.release(); |
845 | } |
846 | |
847 | static void updateLayoutIgnoringPendingStylesheetsIncludingSubframes(Document& document) |
848 | { |
849 | document.updateLayoutIgnorePendingStylesheets(); |
850 | auto* frame = document.frame(); |
851 | for (auto* subframe = frame; subframe; subframe = subframe->tree().traverseNext(frame)) { |
852 | if (auto* document = subframe->document()) |
853 | document->updateLayoutIgnorePendingStylesheets(); |
854 | } |
855 | } |
856 | |
857 | String externalRepresentation(Frame* frame, OptionSet<RenderAsTextFlag> behavior) |
858 | { |
859 | ASSERT(frame); |
860 | ASSERT(frame->document()); |
861 | |
862 | if (!(behavior.contains(RenderAsTextFlag::DontUpdateLayout))) |
863 | updateLayoutIgnoringPendingStylesheetsIncludingSubframes(*frame->document()); |
864 | |
865 | auto* renderer = frame->contentRenderer(); |
866 | if (!renderer) |
867 | return String(); |
868 | |
869 | PrintContext printContext(frame); |
870 | if (behavior.contains(RenderAsTextFlag::PrintingMode)) |
871 | printContext.begin(renderer->width()); |
872 | |
873 | return externalRepresentation(*renderer, behavior); |
874 | } |
875 | |
876 | String externalRepresentation(Element* element, OptionSet<RenderAsTextFlag> behavior) |
877 | { |
878 | ASSERT(element); |
879 | |
880 | // This function doesn't support printing mode. |
881 | ASSERT(!(behavior.contains(RenderAsTextFlag::PrintingMode))); |
882 | |
883 | if (!(behavior.contains(RenderAsTextFlag::DontUpdateLayout))) |
884 | updateLayoutIgnoringPendingStylesheetsIncludingSubframes(element->document()); |
885 | |
886 | auto* renderer = element->renderer(); |
887 | if (!is<RenderBox>(renderer)) |
888 | return String(); |
889 | |
890 | return externalRepresentation(downcast<RenderBox>(*renderer), behavior | RenderAsTextFlag::ShowAllLayers); |
891 | } |
892 | |
893 | static void writeCounterValuesFromChildren(TextStream& stream, const RenderElement* parent, bool& isFirstCounter) |
894 | { |
895 | if (!parent) |
896 | return; |
897 | for (auto& counter : childrenOfType<RenderCounter>(*parent)) { |
898 | if (!isFirstCounter) |
899 | stream << " " ; |
900 | isFirstCounter = false; |
901 | String str(counter.text()); |
902 | stream << str; |
903 | } |
904 | } |
905 | |
906 | String counterValueForElement(Element* element) |
907 | { |
908 | // Make sure the element is not freed during the layout. |
909 | RefPtr<Element> elementRef(element); |
910 | element->document().updateLayout(); |
911 | TextStream stream(TextStream::LineMode::MultipleLine, TextStream::Formatting::SVGStyleRect | TextStream::Formatting::LayoutUnitsAsIntegers); |
912 | bool isFirstCounter = true; |
913 | // The counter renderers should be children of :before or :after pseudo-elements. |
914 | if (PseudoElement* before = element->beforePseudoElement()) |
915 | writeCounterValuesFromChildren(stream, before->renderer(), isFirstCounter); |
916 | if (PseudoElement* after = element->afterPseudoElement()) |
917 | writeCounterValuesFromChildren(stream, after->renderer(), isFirstCounter); |
918 | return stream.release(); |
919 | } |
920 | |
921 | String markerTextForListItem(Element* element) |
922 | { |
923 | // Make sure the element is not freed during the layout. |
924 | RefPtr<Element> elementRef(element); |
925 | element->document().updateLayout(); |
926 | |
927 | RenderElement* renderer = element->renderer(); |
928 | if (!is<RenderListItem>(renderer)) |
929 | return String(); |
930 | |
931 | return downcast<RenderListItem>(*renderer).markerText(); |
932 | } |
933 | |
934 | } // namespace WebCore |
935 | |