1/*
2 * Copyright (C) 2004, 2006, 2007 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 "Document.h"
30#include "FlowThreadController.h"
31#include "Frame.h"
32#include "FrameSelection.h"
33#include "FrameView.h"
34#include "HTMLElement.h"
35#include "HTMLNames.h"
36#include "InlineTextBox.h"
37#include "PrintContext.h"
38#include "PseudoElement.h"
39#include "RenderBlockFlow.h"
40#include "RenderCounter.h"
41#include "RenderDetailsMarker.h"
42#include "RenderFileUploadControl.h"
43#include "RenderInline.h"
44#include "RenderIterator.h"
45#include "RenderLayer.h"
46#include "RenderLayerBacking.h"
47#include "RenderLineBreak.h"
48#include "RenderListItem.h"
49#include "RenderListMarker.h"
50#include "RenderNamedFlowFragment.h"
51#include "RenderNamedFlowThread.h"
52#include "RenderRegion.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/unicode/CharacterNames.h>
71
72namespace WebCore {
73
74using namespace HTMLNames;
75
76static void writeLayers(TextStream&, const RenderLayer* rootLayer, RenderLayer*, const LayoutRect& paintDirtyRect, int indent = 0, RenderAsTextBehavior = RenderAsTextBehaviorNormal);
77
78static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle)
79{
80    switch (borderStyle) {
81        case BNONE:
82            ts << "none";
83            break;
84        case BHIDDEN:
85            ts << "hidden";
86            break;
87        case INSET:
88            ts << "inset";
89            break;
90        case GROOVE:
91            ts << "groove";
92            break;
93        case RIDGE:
94            ts << "ridge";
95            break;
96        case OUTSET:
97            ts << "outset";
98            break;
99        case DOTTED:
100            ts << "dotted";
101            break;
102        case DASHED:
103            ts << "dashed";
104            break;
105        case SOLID:
106            ts << "solid";
107            break;
108        case DOUBLE:
109            ts << "double";
110            break;
111    }
112
113    ts << " ";
114}
115
116static String getTagName(Node* n)
117{
118    if (n->isDocumentNode())
119        return "";
120    if (n->nodeType() == Node::COMMENT_NODE)
121        return "COMMENT";
122    return n->nodeName();
123}
124
125static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
126{
127    if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
128        return false;
129
130    const HTMLElement* elem = toHTMLElement(node);
131    if (elem->getAttribute(classAttr) != "Apple-style-span")
132        return false;
133
134    if (!node->hasChildNodes())
135        return true;
136
137    const StyleProperties* inlineStyleDecl = elem->inlineStyle();
138    return (!inlineStyleDecl || inlineStyleDecl->isEmpty());
139}
140
141String quoteAndEscapeNonPrintables(const String& s)
142{
143    StringBuilder result;
144    result.append('"');
145    for (unsigned i = 0; i != s.length(); ++i) {
146        UChar c = s[i];
147        if (c == '\\') {
148            result.append('\\');
149            result.append('\\');
150        } else if (c == '"') {
151            result.append('\\');
152            result.append('"');
153        } else if (c == '\n' || c == noBreakSpace)
154            result.append(' ');
155        else {
156            if (c >= 0x20 && c < 0x7F)
157                result.append(c);
158            else {
159                result.append('\\');
160                result.append('x');
161                result.append('{');
162                appendUnsignedAsHex(c, result);
163                result.append('}');
164            }
165        }
166    }
167    result.append('"');
168    return result.toString();
169}
170
171void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior)
172{
173    ts << o.renderName();
174
175    if (behavior & RenderAsTextShowAddresses)
176        ts << " " << static_cast<const void*>(&o);
177
178    if (o.style().zIndex())
179        ts << " zI: " << o.style().zIndex();
180
181    if (o.node()) {
182        String tagName = getTagName(o.node());
183        // FIXME: Temporary hack to make tests pass by simulating the old generated content output.
184        if (o.isPseudoElement() || (o.parent() && o.parent()->isPseudoElement()))
185            tagName = emptyAtom;
186        if (!tagName.isEmpty()) {
187            ts << " {" << tagName << "}";
188            // flag empty or unstyled AppleStyleSpan because we never
189            // want to leave them in the DOM
190            if (isEmptyOrUnstyledAppleStyleSpan(o.node()))
191                ts << " *empty or unstyled AppleStyleSpan*";
192        }
193    }
194
195    RenderBlock* cb = o.containingBlock();
196    bool adjustForTableCells = cb ? cb->isTableCell() : false;
197
198    LayoutRect r;
199    if (o.isText()) {
200        // 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
201        // many test results.
202        const RenderText& text = toRenderText(o);
203        r = IntRect(text.firstRunLocation(), text.linesBoundingBox().size());
204        if (!text.firstTextBox() && !text.simpleLineLayout())
205            adjustForTableCells = false;
206    } else if (o.isBR()) {
207        const RenderLineBreak& br = toRenderLineBreak(o);
208        IntRect linesBox = br.linesBoundingBox();
209        r = IntRect(linesBox.x(), linesBox.y(), linesBox.width(), linesBox.height());
210        if (!br.inlineBoxWrapper())
211            adjustForTableCells = false;
212    } else if (o.isRenderInline()) {
213        const RenderInline& inlineFlow = toRenderInline(o);
214        // FIXME: Would be better not to just dump 0, 0 as the x and y here.
215        r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height());
216        adjustForTableCells = false;
217    } else if (o.isTableCell()) {
218        // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect.  We'd like
219        // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are
220        // captured by the results.
221        const RenderTableCell& cell = *toRenderTableCell(&o);
222        r = LayoutRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter());
223    } else if (o.isBox())
224        r = toRenderBox(o).frameRect();
225
226    // FIXME: Temporary in order to ensure compatibility with existing layout test results.
227    if (adjustForTableCells)
228        r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore());
229
230    // FIXME: Convert layout test results to report sub-pixel values, in the meantime using enclosingIntRect
231    // for consistency with old results.
232    ts << " " << enclosingIntRect(r);
233
234    if (!o.isText()) {
235        if (o.isFileUploadControl())
236            ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue());
237
238        if (o.parent()) {
239            Color color = o.style().visitedDependentColor(CSSPropertyColor);
240            if (o.parent()->style().visitedDependentColor(CSSPropertyColor) != color)
241                ts << " [color=" << color.nameForRenderTreeAsText() << "]";
242
243            // Do not dump invalid or transparent backgrounds, since that is the default.
244            Color backgroundColor = o.style().visitedDependentColor(CSSPropertyBackgroundColor);
245            if (o.parent()->style().visitedDependentColor(CSSPropertyBackgroundColor) != backgroundColor
246                && backgroundColor.isValid() && backgroundColor.rgb())
247                ts << " [bgcolor=" << backgroundColor.nameForRenderTreeAsText() << "]";
248
249            Color textFillColor = o.style().visitedDependentColor(CSSPropertyWebkitTextFillColor);
250            if (o.parent()->style().visitedDependentColor(CSSPropertyWebkitTextFillColor) != textFillColor
251                && textFillColor.isValid() && textFillColor != color && textFillColor.rgb())
252                ts << " [textFillColor=" << textFillColor.nameForRenderTreeAsText() << "]";
253
254            Color textStrokeColor = o.style().visitedDependentColor(CSSPropertyWebkitTextStrokeColor);
255            if (o.parent()->style().visitedDependentColor(CSSPropertyWebkitTextStrokeColor) != textStrokeColor
256                && textStrokeColor.isValid() && textStrokeColor != color && textStrokeColor.rgb())
257                ts << " [textStrokeColor=" << textStrokeColor.nameForRenderTreeAsText() << "]";
258
259            if (o.parent()->style().textStrokeWidth() != o.style().textStrokeWidth() && o.style().textStrokeWidth() > 0)
260                ts << " [textStrokeWidth=" << o.style().textStrokeWidth() << "]";
261        }
262
263        if (!o.isBoxModelObject() || o.isLineBreak())
264            return;
265
266        const RenderBoxModelObject& box = toRenderBoxModelObject(o);
267        if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) {
268            ts << " [border:";
269
270            BorderValue prevBorder = o.style().borderTop();
271            if (!box.borderTop())
272                ts << " none";
273            else {
274                ts << " (" << box.borderTop() << "px ";
275                printBorderStyle(ts, o.style().borderTopStyle());
276                Color col = o.style().borderTopColor();
277                if (!col.isValid())
278                    col = o.style().color();
279                ts << col.nameForRenderTreeAsText() << ")";
280            }
281
282            if (o.style().borderRight() != prevBorder) {
283                prevBorder = o.style().borderRight();
284                if (!box.borderRight())
285                    ts << " none";
286                else {
287                    ts << " (" << box.borderRight() << "px ";
288                    printBorderStyle(ts, o.style().borderRightStyle());
289                    Color col = o.style().borderRightColor();
290                    if (!col.isValid())
291                        col = o.style().color();
292                    ts << col.nameForRenderTreeAsText() << ")";
293                }
294            }
295
296            if (o.style().borderBottom() != prevBorder) {
297                prevBorder = box.style().borderBottom();
298                if (!box.borderBottom())
299                    ts << " none";
300                else {
301                    ts << " (" << box.borderBottom() << "px ";
302                    printBorderStyle(ts, o.style().borderBottomStyle());
303                    Color col = o.style().borderBottomColor();
304                    if (!col.isValid())
305                        col = o.style().color();
306                    ts << col.nameForRenderTreeAsText() << ")";
307                }
308            }
309
310            if (o.style().borderLeft() != prevBorder) {
311                prevBorder = o.style().borderLeft();
312                if (!box.borderLeft())
313                    ts << " none";
314                else {
315                    ts << " (" << box.borderLeft() << "px ";
316                    printBorderStyle(ts, o.style().borderLeftStyle());
317                    Color col = o.style().borderLeftColor();
318                    if (!col.isValid())
319                        col = o.style().color();
320                    ts << col.nameForRenderTreeAsText() << ")";
321                }
322            }
323
324            ts << "]";
325        }
326
327#if ENABLE(MATHML)
328        // We want to show any layout padding, both CSS padding and intrinsic padding, so we can't just check o.style().hasPadding().
329        if (o.isRenderMathMLBlock() && (box.paddingTop() || box.paddingRight() || box.paddingBottom() || box.paddingLeft())) {
330            ts << " [";
331            LayoutUnit cssTop = box.computedCSSPaddingTop();
332            LayoutUnit cssRight = box.computedCSSPaddingRight();
333            LayoutUnit cssBottom = box.computedCSSPaddingBottom();
334            LayoutUnit cssLeft = box.computedCSSPaddingLeft();
335            if (box.paddingTop() != cssTop || box.paddingRight() != cssRight || box.paddingBottom() != cssBottom || box.paddingLeft() != cssLeft) {
336                ts << "intrinsic ";
337                if (cssTop || cssRight || cssBottom || cssLeft)
338                    ts << "+ CSS ";
339            }
340            ts << "padding: " << roundToInt(box.paddingTop()) << " " << roundToInt(box.paddingRight()) << " " << roundToInt(box.paddingBottom()) << " " << roundToInt(box.paddingLeft()) << "]";
341        }
342#endif
343    }
344
345    if (o.isTableCell()) {
346        const RenderTableCell& c = *toRenderTableCell(&o);
347        ts << " [r=" << c.rowIndex() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
348    }
349
350#if ENABLE(DETAILS_ELEMENT)
351    if (o.isDetailsMarker()) {
352        ts << ": ";
353        switch (toRenderDetailsMarker(o).orientation()) {
354        case RenderDetailsMarker::Left:
355            ts << "left";
356            break;
357        case RenderDetailsMarker::Right:
358            ts << "right";
359            break;
360        case RenderDetailsMarker::Up:
361            ts << "up";
362            break;
363        case RenderDetailsMarker::Down:
364            ts << "down";
365            break;
366        }
367    }
368#endif
369
370    if (o.isListMarker()) {
371        String text = toRenderListMarker(o).text();
372        if (!text.isEmpty()) {
373            if (text.length() != 1)
374                text = quoteAndEscapeNonPrintables(text);
375            else {
376                switch (text[0]) {
377                    case bullet:
378                        text = "bullet";
379                        break;
380                    case blackSquare:
381                        text = "black square";
382                        break;
383                    case whiteBullet:
384                        text = "white bullet";
385                        break;
386                    default:
387                        text = quoteAndEscapeNonPrintables(text);
388                }
389            }
390            ts << ": " << text;
391        }
392    }
393
394    if (behavior & RenderAsTextShowIDAndClass) {
395        if (Element* element = o.node() && o.node()->isElementNode() ? toElement(o.node()) : 0) {
396            if (element->hasID())
397                ts << " id=\"" + element->getIdAttribute() + "\"";
398
399            if (element->hasClass()) {
400                ts << " class=\"";
401                for (size_t i = 0; i < element->classNames().size(); ++i) {
402                    if (i > 0)
403                        ts << " ";
404                    ts << element->classNames()[i];
405                }
406                ts << "\"";
407            }
408        }
409    }
410
411    if (behavior & RenderAsTextShowLayoutState) {
412        bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout();
413        if (needsLayout)
414            ts << " (needs layout:";
415
416        bool havePrevious = false;
417        if (o.selfNeedsLayout()) {
418            ts << " self";
419            havePrevious = true;
420        }
421
422        if (o.needsPositionedMovementLayout()) {
423            if (havePrevious)
424                ts << ",";
425            havePrevious = true;
426            ts << " positioned movement";
427        }
428
429        if (o.normalChildNeedsLayout()) {
430            if (havePrevious)
431                ts << ",";
432            havePrevious = true;
433            ts << " child";
434        }
435
436        if (o.posChildNeedsLayout()) {
437            if (havePrevious)
438                ts << ",";
439            ts << " positioned child";
440        }
441
442        if (needsLayout)
443            ts << ")";
444    }
445
446    if (behavior & RenderAsTextShowOverflow && o.isBox()) {
447        const RenderBox& box = toRenderBox(o);
448        if (box.hasRenderOverflow()) {
449            LayoutRect layoutOverflow = box.layoutOverflowRect();
450            ts << " (layout overflow " << layoutOverflow.x().toInt() << "," << layoutOverflow.y().toInt() << " " << layoutOverflow.width().toInt() << "x" << layoutOverflow.height().toInt() << ")";
451
452            if (box.hasVisualOverflow()) {
453                LayoutRect visualOverflow = box.visualOverflowRect();
454                ts << " (visual overflow " << visualOverflow.x().toInt() << "," << visualOverflow.y().toInt() << " " << visualOverflow.width().toInt() << "x" << visualOverflow.height().toInt() << ")";
455            }
456        }
457    }
458}
459
460static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run)
461{
462    // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder
463    // to detect any changes caused by the conversion to floating point. :(
464    int x = run.x();
465    int y = run.y();
466    int logicalWidth = ceilf(run.left() + run.logicalWidth()) - x;
467
468    // FIXME: Table cell adjustment is temporary until results can be updated.
469    if (o.containingBlock()->isTableCell())
470        y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore();
471
472    ts << "text run at (" << x << "," << y << ") width " << logicalWidth;
473    if (!run.isLeftToRightDirection() || run.dirOverride()) {
474        ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR");
475        if (run.dirOverride())
476            ts << " override";
477    }
478    ts << ": "
479        << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len()));
480    if (run.hasHyphen())
481        ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style().hyphenString());
482    ts << "\n";
483}
484
485static void writeSimpleLine(TextStream& ts, const RenderText& o, const LayoutRect& rect, const String& text)
486{
487    int x = rect.x();
488    int y = rect.y();
489    int logicalWidth = ceilf(rect.x() + rect.width()) - x;
490
491    if (o.containingBlock()->isTableCell())
492        y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore();
493
494    ts << "text run at (" << x << "," << y << ") width " << logicalWidth;
495    ts << ": "
496        << quoteAndEscapeNonPrintables(text);
497    ts << "\n";
498}
499
500void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior)
501{
502    if (o.isSVGShape()) {
503        write(ts, *toRenderSVGShape(&o), indent);
504        return;
505    }
506    if (o.isSVGGradientStop()) {
507        writeSVGGradientStop(ts, *toRenderSVGGradientStop(&o), indent);
508        return;
509    }
510    if (o.isSVGResourceContainer()) {
511        writeSVGResourceContainer(ts, toRenderSVGResourceContainer(o), indent);
512        return;
513    }
514    if (o.isSVGContainer()) {
515        writeSVGContainer(ts, toRenderSVGContainer(o), indent);
516        return;
517    }
518    if (o.isSVGRoot()) {
519        write(ts, *toRenderSVGRoot(&o), indent);
520        return;
521    }
522    if (o.isSVGText()) {
523        writeSVGText(ts, *toRenderSVGText(&o), indent);
524        return;
525    }
526    if (o.isSVGInlineText()) {
527        writeSVGInlineText(ts, toRenderSVGInlineText(o), indent);
528        return;
529    }
530    if (o.isSVGImage()) {
531        writeSVGImage(ts, *toRenderSVGImage(&o), indent);
532        return;
533    }
534
535    writeIndent(ts, indent);
536
537    RenderTreeAsText::writeRenderObject(ts, o, behavior);
538    ts << "\n";
539
540    if (o.isText()) {
541        auto& text = toRenderText(o);
542        if (auto layout = text.simpleLineLayout()) {
543            ASSERT(!text.firstTextBox());
544            auto resolver = runResolver(toRenderBlockFlow(*text.parent()), *layout);
545            for (auto it = resolver.begin(), end = resolver.end(); it != end; ++it) {
546                auto run = *it;
547                writeIndent(ts, indent + 1);
548                writeSimpleLine(ts, text, run.rect(), run.text());
549            }
550        } else {
551            for (auto box = text.firstTextBox(); box; box = box->nextTextBox()) {
552                writeIndent(ts, indent + 1);
553                writeTextRun(ts, text, *box);
554            }
555        }
556
557    } else {
558        for (RenderObject* child = toRenderElement(o).firstChild(); child; child = child->nextSibling()) {
559            if (child->hasLayer())
560                continue;
561            write(ts, *child, indent + 1, behavior);
562        }
563    }
564
565    if (o.isWidget()) {
566        Widget* widget = toRenderWidget(&o)->widget();
567        if (widget && widget->isFrameView()) {
568            FrameView* view = toFrameView(widget);
569            if (RenderView* root = view->frame().contentRenderer()) {
570                if (!(behavior & RenderAsTextDontUpdateLayout))
571                    view->layout();
572                RenderLayer* l = root->layer();
573                if (l)
574                    writeLayers(ts, l, l, l->rect(), indent + 1, behavior);
575            }
576        }
577    }
578}
579
580enum LayerPaintPhase {
581    LayerPaintPhaseAll = 0,
582    LayerPaintPhaseBackground = -1,
583    LayerPaintPhaseForeground = 1
584};
585
586static void write(TextStream& ts, RenderLayer& l,
587                  const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, const LayoutRect& clipRect, const LayoutRect& outlineClipRect,
588                  LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal)
589{
590    IntRect adjustedLayoutBounds = pixelSnappedIntRect(layerBounds);
591    IntRect adjustedBackgroundClipRect = pixelSnappedIntRect(backgroundClipRect);
592    IntRect adjustedClipRect = pixelSnappedIntRect(clipRect);
593    IntRect adjustedOutlineClipRect = pixelSnappedIntRect(outlineClipRect);
594
595    writeIndent(ts, indent);
596
597    ts << "layer ";
598
599    if (behavior & RenderAsTextShowAddresses)
600        ts << static_cast<const void*>(&l) << " ";
601
602    ts << adjustedLayoutBounds;
603
604    if (!adjustedLayoutBounds.isEmpty()) {
605        if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds))
606            ts << " backgroundClip " << adjustedBackgroundClipRect;
607        if (!adjustedClipRect.contains(adjustedLayoutBounds))
608            ts << " clip " << adjustedClipRect;
609        if (!adjustedOutlineClipRect.contains(adjustedLayoutBounds))
610            ts << " outlineClip " << adjustedOutlineClipRect;
611    }
612
613    if (l.renderer().hasOverflowClip()) {
614        if (l.scrollXOffset())
615            ts << " scrollX " << l.scrollXOffset();
616        if (l.scrollYOffset())
617            ts << " scrollY " << l.scrollYOffset();
618        if (l.renderBox() && l.renderBox()->pixelSnappedClientWidth() != l.scrollWidth())
619            ts << " scrollWidth " << l.scrollWidth();
620        if (l.renderBox() && l.renderBox()->pixelSnappedClientHeight() != l.scrollHeight())
621            ts << " scrollHeight " << l.scrollHeight();
622    }
623
624    if (paintPhase == LayerPaintPhaseBackground)
625        ts << " layerType: background only";
626    else if (paintPhase == LayerPaintPhaseForeground)
627        ts << " layerType: foreground only";
628
629    if (behavior & RenderAsTextShowCompositedLayers) {
630        if (l.isComposited())
631            ts << " (composited, bounds=" << l.backing()->compositedBounds() << ", drawsContent=" << l.backing()->graphicsLayer()->drawsContent() << ", paints into ancestor=" << l.backing()->paintsIntoCompositedAncestor() << ")";
632    }
633
634#if ENABLE(CSS_COMPOSITING)
635    if (l.isolatesBlending())
636        ts << " isolatesBlending";
637    if (l.hasBlendMode())
638        ts << " blendMode: " << compositeOperatorName(CompositeSourceOver, l.blendMode());
639#endif
640
641    ts << "\n";
642
643    if (paintPhase != LayerPaintPhaseBackground)
644        write(ts, l.renderer(), indent + 1, behavior);
645}
646
647static void writeRenderRegionList(const RenderRegionList& flowThreadRegionList, TextStream& ts, int indent)
648{
649    for (const auto& renderRegion : flowThreadRegionList) {
650        writeIndent(ts, indent);
651        ts << static_cast<const RenderObject*>(renderRegion)->renderName();
652
653        Element* generatingElement = renderRegion->generatingElement();
654        if (generatingElement) {
655            bool isRenderNamedFlowFragment = renderRegion->isRenderNamedFlowFragment();
656            if (isRenderNamedFlowFragment && toRenderNamedFlowFragment(renderRegion)->hasCustomRegionStyle())
657                ts << " region style: 1";
658            if (renderRegion->hasAutoLogicalHeight())
659                ts << " hasAutoLogicalHeight";
660
661            if (isRenderNamedFlowFragment)
662                ts << " (anonymous child of";
663
664            StringBuilder tagName;
665            tagName.append(generatingElement->nodeName());
666
667            RenderElement* renderElementForRegion = isRenderNamedFlowFragment ? renderRegion->parent() : renderRegion;
668            if (renderElementForRegion->isPseudoElement()) {
669                if (renderElementForRegion->element()->isBeforePseudoElement())
670                    tagName.append("::before");
671                else if (renderElementForRegion->element()->isAfterPseudoElement())
672                    tagName.append("::after");
673            }
674
675            ts << " {" << tagName.toString() << "}";
676
677            if (generatingElement->hasID())
678                ts << " #" << generatingElement->idForStyleResolution();
679
680            if (isRenderNamedFlowFragment)
681                ts << ")";
682        }
683
684        ts << "\n";
685    }
686}
687
688static void writeRenderNamedFlowThreads(TextStream& ts, RenderView& renderView, const RenderLayer* rootLayer,
689    const LayoutRect& paintRect, int indent, RenderAsTextBehavior behavior)
690{
691    if (!renderView.hasRenderNamedFlowThreads())
692        return;
693
694    const RenderNamedFlowThreadList* list = renderView.flowThreadController().renderNamedFlowThreadList();
695
696    writeIndent(ts, indent);
697    ts << "Named flows\n";
698
699    for (RenderNamedFlowThreadList::const_iterator iter = list->begin(); iter != list->end(); ++iter) {
700        const RenderNamedFlowThread* renderFlowThread = *iter;
701
702        writeIndent(ts, indent + 1);
703        ts << "Named flow '" << renderFlowThread->flowThreadName() << "'\n";
704
705        RenderLayer* layer = renderFlowThread->layer();
706        writeLayers(ts, rootLayer, layer, paintRect, indent + 2, behavior);
707
708        // Display the valid and invalid render regions attached to this flow thread.
709        const RenderRegionList& validRegionsList = renderFlowThread->renderRegionList();
710        const RenderRegionList& invalidRegionsList = renderFlowThread->invalidRenderRegionList();
711        if (!validRegionsList.isEmpty()) {
712            writeIndent(ts, indent + 2);
713            ts << "Regions for named flow '" << renderFlowThread->flowThreadName() << "'\n";
714            writeRenderRegionList(validRegionsList, ts, indent + 3);
715        }
716        if (!invalidRegionsList.isEmpty()) {
717            writeIndent(ts, indent + 2);
718            ts << "Invalid regions for named flow '" << renderFlowThread->flowThreadName() << "'\n";
719            writeRenderRegionList(invalidRegionsList, ts, indent + 3);
720        }
721    }
722}
723
724static LayoutSize maxLayoutOverflow(const RenderBox* box)
725{
726    LayoutRect overflowRect = box->layoutOverflowRect();
727    return LayoutSize(overflowRect.maxX(), overflowRect.maxY());
728}
729
730static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l,
731                        const LayoutRect& paintRect, int indent, RenderAsTextBehavior behavior)
732{
733    // FIXME: Apply overflow to the root layer to not break every test.  Complete hack.  Sigh.
734    LayoutRect paintDirtyRect(paintRect);
735    if (rootLayer == l) {
736        paintDirtyRect.setWidth(std::max<LayoutUnit>(paintDirtyRect.width(), rootLayer->renderBox()->layoutOverflowRect().maxX()));
737        paintDirtyRect.setHeight(std::max<LayoutUnit>(paintDirtyRect.height(), rootLayer->renderBox()->layoutOverflowRect().maxY()));
738        l->setSize(l->size().expandedTo(pixelSnappedIntSize(maxLayoutOverflow(l->renderBox()), LayoutPoint(0, 0))));
739    }
740
741    // Calculate the clip rects we should use.
742    LayoutRect layerBounds;
743    ClipRect damageRect, clipRectToApply, outlineRect;
744    l->calculateRects(RenderLayer::ClipRectsContext(rootLayer, TemporaryClipRects), paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, l->offsetFromAncestor(rootLayer));
745
746    // Ensure our lists are up-to-date.
747    l->updateLayerListsIfNeeded();
748
749    bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : l->intersectsDamageRect(layerBounds, damageRect.rect(), rootLayer, l->offsetFromAncestor(rootLayer));
750    Vector<RenderLayer*>* negList = l->negZOrderList();
751    bool paintsBackgroundSeparately = negList && negList->size() > 0;
752    if (shouldPaint && paintsBackgroundSeparately)
753        write(ts, *l, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), LayerPaintPhaseBackground, indent, behavior);
754
755    if (negList) {
756        int currIndent = indent;
757        if (behavior & RenderAsTextShowLayerNesting) {
758            writeIndent(ts, indent);
759            ts << " negative z-order list(" << negList->size() << ")\n";
760            ++currIndent;
761        }
762        for (unsigned i = 0; i != negList->size(); ++i)
763            writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, currIndent, behavior);
764    }
765
766    if (shouldPaint)
767        write(ts, *l, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior);
768
769    if (Vector<RenderLayer*>* normalFlowList = l->normalFlowList()) {
770        int currIndent = indent;
771        if (behavior & RenderAsTextShowLayerNesting) {
772            writeIndent(ts, indent);
773            ts << " normal flow list(" << normalFlowList->size() << ")\n";
774            ++currIndent;
775        }
776        for (unsigned i = 0; i != normalFlowList->size(); ++i)
777            writeLayers(ts, rootLayer, normalFlowList->at(i), paintDirtyRect, currIndent, behavior);
778    }
779
780    if (Vector<RenderLayer*>* posList = l->posZOrderList()) {
781        size_t layerCount = 0;
782        for (unsigned i = 0; i != posList->size(); ++i)
783            if (!posList->at(i)->isFlowThreadCollectingGraphicsLayersUnderRegions())
784                ++layerCount;
785        if (layerCount) {
786            int currIndent = indent;
787            // We only print the header if there's at list a non-RenderNamedFlowThread part of the list.
788            if (!posList->size() || !posList->at(0)->isFlowThreadCollectingGraphicsLayersUnderRegions()) {
789                if (behavior & RenderAsTextShowLayerNesting) {
790                    writeIndent(ts, indent);
791                    ts << " positive z-order list(" << posList->size() << ")\n";
792                    ++currIndent;
793                }
794                for (unsigned i = 0; i != posList->size(); ++i) {
795                    // Do not print named flows twice.
796                    if (!posList->at(i)->isFlowThreadCollectingGraphicsLayersUnderRegions())
797                        writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, currIndent, behavior);
798                }
799            }
800        }
801    }
802
803    // Altough the RenderFlowThread requires a layer, it is not collected by its parent,
804    // so we have to treat it as a special case.
805    if (l->renderer().isRenderView())
806        writeRenderNamedFlowThreads(ts, toRenderView(l->renderer()), rootLayer, paintDirtyRect, indent, behavior);
807}
808
809static String nodePosition(Node* node)
810{
811    StringBuilder result;
812
813    Element* body = node->document().body();
814    Node* parent;
815    for (Node* n = node; n; n = parent) {
816        parent = n->parentOrShadowHostNode();
817        if (n != node)
818            result.appendLiteral(" of ");
819        if (parent) {
820            if (body && n == body) {
821                // We don't care what offset body may be in the document.
822                result.appendLiteral("body");
823                break;
824            }
825            if (n->isShadowRoot()) {
826                result.append('{');
827                result.append(getTagName(n));
828                result.append('}');
829            } else {
830                result.appendLiteral("child ");
831                result.appendNumber(n->nodeIndex());
832                result.appendLiteral(" {");
833                result.append(getTagName(n));
834                result.append('}');
835            }
836        } else
837            result.appendLiteral("document");
838    }
839
840    return result.toString();
841}
842
843static void writeSelection(TextStream& ts, const RenderObject* renderer)
844{
845    if (!renderer->isRenderView())
846        return;
847
848    Frame* frame = renderer->document().frame();
849    if (!frame)
850        return;
851
852    VisibleSelection selection = frame->selection().selection();
853    if (selection.isCaret()) {
854        ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode());
855        if (selection.affinity() == UPSTREAM)
856            ts << " (upstream affinity)";
857        ts << "\n";
858    } else if (selection.isRange())
859        ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()) << "\n"
860           << "selection end:   position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().deprecatedNode()) << "\n";
861}
862
863static String externalRepresentation(RenderBox* renderer, RenderAsTextBehavior behavior)
864{
865    TextStream ts;
866    if (!renderer->hasLayer())
867        return ts.release();
868
869    RenderLayer* layer = renderer->layer();
870    writeLayers(ts, layer, layer, layer->rect(), 0, behavior);
871    writeSelection(ts, renderer);
872    return ts.release();
873}
874
875String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior)
876{
877    RenderObject* renderer = frame->contentRenderer();
878    if (!renderer || !renderer->isBox())
879        return String();
880
881    PrintContext printContext(frame);
882    if (behavior & RenderAsTextPrintingMode)
883        printContext.begin(toRenderBox(renderer)->width());
884    if (!(behavior & RenderAsTextDontUpdateLayout))
885        frame->document()->updateLayout();
886
887    return externalRepresentation(toRenderBox(renderer), behavior);
888}
889
890String externalRepresentation(Element* element, RenderAsTextBehavior behavior)
891{
892    RenderObject* renderer = element->renderer();
893    if (!renderer || !renderer->isBox())
894        return String();
895    // Doesn't support printing mode.
896    ASSERT(!(behavior & RenderAsTextPrintingMode));
897    if (!(behavior & RenderAsTextDontUpdateLayout))
898        element->document().updateLayout();
899
900    return externalRepresentation(toRenderBox(renderer), behavior | RenderAsTextShowAllLayers);
901}
902
903static void writeCounterValuesFromChildren(TextStream& stream, const RenderElement* parent, bool& isFirstCounter)
904{
905    if (!parent)
906        return;
907    for (auto& counter : childrenOfType<RenderCounter>(*parent)) {
908        if (!isFirstCounter)
909            stream << " ";
910        isFirstCounter = false;
911        String str(counter.text());
912        stream << str;
913    }
914}
915
916String counterValueForElement(Element* element)
917{
918    // Make sure the element is not freed during the layout.
919    RefPtr<Element> elementRef(element);
920    element->document().updateLayout();
921    TextStream stream;
922    bool isFirstCounter = true;
923    // The counter renderers should be children of :before or :after pseudo-elements.
924    if (PseudoElement* before = element->beforePseudoElement())
925        writeCounterValuesFromChildren(stream, before->renderer(), isFirstCounter);
926    if (PseudoElement* after = element->afterPseudoElement())
927        writeCounterValuesFromChildren(stream, after->renderer(), isFirstCounter);
928    return stream.release();
929}
930
931String markerTextForListItem(Element* element)
932{
933    // Make sure the element is not freed during the layout.
934    RefPtr<Element> elementRef(element);
935    element->document().updateLayout();
936
937    RenderObject* renderer = element->renderer();
938    if (!renderer || !renderer->isListItem())
939        return String();
940
941    return toRenderListItem(renderer)->markerText();
942}
943
944} // namespace WebCore
945