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