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
77namespace WebCore {
78
79using namespace HTMLNames;
80
81static void writeLayers(TextStream&, const RenderLayer& rootLayer, RenderLayer&, const LayoutRect& paintDirtyRect, OptionSet<RenderAsTextFlag>);
82
83static 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
121static 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
130static 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
146String 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
172void 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
412void 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
480static 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
505static 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
522void 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
602enum LayerPaintPhase {
603 LayerPaintPhaseAll = 0,
604 LayerPaintPhaseBackground = -1,
605 LayerPaintPhaseForeground = 1
606};
607
608static 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
671static 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
679static LayoutSize maxLayoutOverflow(const RenderBox* box)
680{
681 LayoutRect overflowRect = box->layoutOverflowRect();
682 return LayoutSize(overflowRect.maxX(), overflowRect.maxY());
683}
684
685static 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
779static 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
813static 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
833static 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
847static 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
857String 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
876String 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
893static 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
906String 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
921String 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