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 "core/rendering/RenderTreeAsText.h"
28
29#include "core/HTMLNames.h"
30#include "core/css/StylePropertySet.h"
31#include "core/dom/Document.h"
32#include "core/editing/FrameSelection.h"
33#include "core/frame/FrameView.h"
34#include "core/frame/LocalFrame.h"
35#include "core/html/HTMLElement.h"
36#include "core/page/PrintContext.h"
37#include "core/rendering/FlowThreadController.h"
38#include "core/rendering/InlineTextBox.h"
39#include "core/rendering/RenderBR.h"
40#include "core/rendering/RenderDetailsMarker.h"
41#include "core/rendering/RenderFileUploadControl.h"
42#include "core/rendering/RenderFlowThread.h"
43#include "core/rendering/RenderInline.h"
44#include "core/rendering/RenderLayer.h"
45#include "core/rendering/RenderListItem.h"
46#include "core/rendering/RenderListMarker.h"
47#include "core/rendering/RenderPart.h"
48#include "core/rendering/RenderTableCell.h"
49#include "core/rendering/RenderView.h"
50#include "core/rendering/RenderWidget.h"
51#include "core/rendering/compositing/CompositedLayerMapping.h"
52#include "core/rendering/svg/RenderSVGContainer.h"
53#include "core/rendering/svg/RenderSVGGradientStop.h"
54#include "core/rendering/svg/RenderSVGImage.h"
55#include "core/rendering/svg/RenderSVGInlineText.h"
56#include "core/rendering/svg/RenderSVGPath.h"
57#include "core/rendering/svg/RenderSVGRoot.h"
58#include "core/rendering/svg/RenderSVGText.h"
59#include "core/rendering/svg/SVGRenderTreeAsText.h"
60#include "wtf/HexNumber.h"
61#include "wtf/Vector.h"
62#include "wtf/unicode/CharacterNames.h"
63
64namespace blink {
65
66using namespace HTMLNames;
67
68static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle)
69{
70    switch (borderStyle) {
71        case BNONE:
72            ts << "none";
73            break;
74        case BHIDDEN:
75            ts << "hidden";
76            break;
77        case INSET:
78            ts << "inset";
79            break;
80        case GROOVE:
81            ts << "groove";
82            break;
83        case RIDGE:
84            ts << "ridge";
85            break;
86        case OUTSET:
87            ts << "outset";
88            break;
89        case DOTTED:
90            ts << "dotted";
91            break;
92        case DASHED:
93            ts << "dashed";
94            break;
95        case SOLID:
96            ts << "solid";
97            break;
98        case DOUBLE:
99            ts << "double";
100            break;
101    }
102
103    ts << " ";
104}
105
106static String getTagName(Node* n)
107{
108    if (n->isDocumentNode())
109        return "";
110    if (n->nodeType() == Node::COMMENT_NODE)
111        return "COMMENT";
112    return n->nodeName();
113}
114
115static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
116{
117    if (!isHTMLSpanElement(node))
118        return false;
119
120    const HTMLElement& elem = toHTMLElement(*node);
121    if (elem.getAttribute(classAttr) != "Apple-style-span")
122        return false;
123
124    if (!elem.hasChildren())
125        return true;
126
127    const StylePropertySet* inlineStyleDecl = elem.inlineStyle();
128    return (!inlineStyleDecl || inlineStyleDecl->isEmpty());
129}
130
131String quoteAndEscapeNonPrintables(const String& s)
132{
133    StringBuilder result;
134    result.append('"');
135    for (unsigned i = 0; i != s.length(); ++i) {
136        UChar c = s[i];
137        if (c == '\\') {
138            result.append('\\');
139            result.append('\\');
140        } else if (c == '"') {
141            result.append('\\');
142            result.append('"');
143        } else if (c == '\n' || c == noBreakSpace)
144            result.append(' ');
145        else {
146            if (c >= 0x20 && c < 0x7F)
147                result.append(c);
148            else {
149                result.append('\\');
150                result.append('x');
151                result.append('{');
152                appendUnsignedAsHex(c, result);
153                result.append('}');
154            }
155        }
156    }
157    result.append('"');
158    return result.toString();
159}
160
161TextStream& operator<<(TextStream& ts, const Color& c)
162{
163    return ts << c.nameForRenderTreeAsText();
164}
165
166void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior)
167{
168    ts << o.renderName();
169
170    if (behavior & RenderAsTextShowAddresses)
171        ts << " " << static_cast<const void*>(&o);
172
173    if (o.style() && o.style()->zIndex())
174        ts << " zI: " << o.style()->zIndex();
175
176    if (o.node()) {
177        String tagName = getTagName(o.node());
178        // FIXME: Temporary hack to make tests pass by simulating the old generated content output.
179        if (o.isPseudoElement() || (o.parent() && o.parent()->isPseudoElement()))
180            tagName = emptyAtom;
181        if (!tagName.isEmpty()) {
182            ts << " {" << tagName << "}";
183            // flag empty or unstyled AppleStyleSpan because we never
184            // want to leave them in the DOM
185            if (isEmptyOrUnstyledAppleStyleSpan(o.node()))
186                ts << " *empty or unstyled AppleStyleSpan*";
187        }
188    }
189
190    RenderBlock* cb = o.containingBlock();
191    bool adjustForTableCells = cb ? cb->isTableCell() : false;
192
193    LayoutRect r;
194    if (o.isText()) {
195        // 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
196        // many test results.
197        const RenderText& text = toRenderText(o);
198        IntRect linesBox = text.linesBoundingBox();
199        r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height());
200        if (adjustForTableCells && !text.firstTextBox())
201            adjustForTableCells = false;
202    } else if (o.isRenderInline()) {
203        // FIXME: Would be better not to just dump 0, 0 as the x and y here.
204        const RenderInline& inlineFlow = toRenderInline(o);
205        r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height());
206        adjustForTableCells = false;
207    } else if (o.isTableCell()) {
208        // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect.  We'd like
209        // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are
210        // captured by the results.
211        const RenderTableCell& cell = toRenderTableCell(o);
212        r = LayoutRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter());
213    } else if (o.isBox())
214        r = toRenderBox(&o)->frameRect();
215
216    // FIXME: Temporary in order to ensure compatibility with existing layout test results.
217    if (adjustForTableCells)
218        r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore());
219
220    ts << " " << r;
221
222    if (!(o.isText() && !o.isBR())) {
223        if (o.isFileUploadControl())
224            ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue());
225
226        if (o.parent()) {
227            Color color = o.resolveColor(CSSPropertyColor);
228            if (o.parent()->resolveColor(CSSPropertyColor) != color)
229                ts << " [color=" << color << "]";
230
231            // Do not dump invalid or transparent backgrounds, since that is the default.
232            Color backgroundColor = o.resolveColor(CSSPropertyBackgroundColor);
233            if (o.parent()->resolveColor(CSSPropertyBackgroundColor) != backgroundColor
234                && backgroundColor.rgb())
235                ts << " [bgcolor=" << backgroundColor << "]";
236
237            Color textFillColor = o.resolveColor(CSSPropertyWebkitTextFillColor);
238            if (o.parent()->resolveColor(CSSPropertyWebkitTextFillColor) != textFillColor
239                && textFillColor != color && textFillColor.rgb())
240                ts << " [textFillColor=" << textFillColor << "]";
241
242            Color textStrokeColor = o.resolveColor(CSSPropertyWebkitTextStrokeColor);
243            if (o.parent()->resolveColor(CSSPropertyWebkitTextStrokeColor) != textStrokeColor
244                && textStrokeColor != color && textStrokeColor.rgb())
245                ts << " [textStrokeColor=" << textStrokeColor << "]";
246
247            if (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth() && o.style()->textStrokeWidth() > 0)
248                ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]";
249        }
250
251        if (!o.isBoxModelObject())
252            return;
253
254        const RenderBoxModelObject& box = toRenderBoxModelObject(o);
255        if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) {
256            ts << " [border:";
257
258            BorderValue prevBorder = o.style()->borderTop();
259            if (!box.borderTop())
260                ts << " none";
261            else {
262                ts << " (" << box.borderTop() << "px ";
263                printBorderStyle(ts, o.style()->borderTopStyle());
264                ts << o.resolveColor(CSSPropertyBorderTopColor) << ")";
265            }
266
267            if (o.style()->borderRight() != prevBorder) {
268                prevBorder = o.style()->borderRight();
269                if (!box.borderRight())
270                    ts << " none";
271                else {
272                    ts << " (" << box.borderRight() << "px ";
273                    printBorderStyle(ts, o.style()->borderRightStyle());
274                    ts << o.resolveColor(CSSPropertyBorderRightColor) << ")";
275                }
276            }
277
278            if (o.style()->borderBottom() != prevBorder) {
279                prevBorder = box.style()->borderBottom();
280                if (!box.borderBottom())
281                    ts << " none";
282                else {
283                    ts << " (" << box.borderBottom() << "px ";
284                    printBorderStyle(ts, o.style()->borderBottomStyle());
285                    ts << o.resolveColor(CSSPropertyBorderBottomColor) << ")";
286                }
287            }
288
289            if (o.style()->borderLeft() != prevBorder) {
290                prevBorder = o.style()->borderLeft();
291                if (!box.borderLeft())
292                    ts << " none";
293                else {
294                    ts << " (" << box.borderLeft() << "px ";
295                    printBorderStyle(ts, o.style()->borderLeftStyle());
296                    ts << o.resolveColor(CSSPropertyBorderLeftColor) << ")";
297                }
298            }
299
300            ts << "]";
301        }
302    }
303
304    if (o.isTableCell()) {
305        const RenderTableCell& c = toRenderTableCell(o);
306        ts << " [r=" << c.rowIndex() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
307    }
308
309    if (o.isDetailsMarker()) {
310        ts << ": ";
311        switch (toRenderDetailsMarker(&o)->orientation()) {
312        case RenderDetailsMarker::Left:
313            ts << "left";
314            break;
315        case RenderDetailsMarker::Right:
316            ts << "right";
317            break;
318        case RenderDetailsMarker::Up:
319            ts << "up";
320            break;
321        case RenderDetailsMarker::Down:
322            ts << "down";
323            break;
324        }
325    }
326
327    if (o.isListMarker()) {
328        String text = toRenderListMarker(o).text();
329        if (!text.isEmpty()) {
330            if (text.length() != 1)
331                text = quoteAndEscapeNonPrintables(text);
332            else {
333                switch (text[0]) {
334                    case bullet:
335                        text = "bullet";
336                        break;
337                    case blackSquare:
338                        text = "black square";
339                        break;
340                    case whiteBullet:
341                        text = "white bullet";
342                        break;
343                    default:
344                        text = quoteAndEscapeNonPrintables(text);
345                }
346            }
347            ts << ": " << text;
348        }
349    }
350
351    if (behavior & RenderAsTextShowIDAndClass) {
352        Node* node = o.node();
353        if (node && node->isElementNode()) {
354            Element& element = toElement(*node);
355            if (element.hasID())
356                ts << " id=\"" + element.getIdAttribute() + "\"";
357
358            if (element.hasClass()) {
359                ts << " class=\"";
360                for (size_t i = 0; i < element.classNames().size(); ++i) {
361                    if (i > 0)
362                        ts << " ";
363                    ts << element.classNames()[i];
364                }
365                ts << "\"";
366            }
367        }
368    }
369
370    if (behavior & RenderAsTextShowLayoutState) {
371        bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout();
372        if (needsLayout)
373            ts << " (needs layout:";
374
375        bool havePrevious = false;
376        if (o.selfNeedsLayout()) {
377            ts << " self";
378            havePrevious = true;
379        }
380
381        if (o.needsPositionedMovementLayout()) {
382            if (havePrevious)
383                ts << ",";
384            havePrevious = true;
385            ts << " positioned movement";
386        }
387
388        if (o.normalChildNeedsLayout()) {
389            if (havePrevious)
390                ts << ",";
391            havePrevious = true;
392            ts << " child";
393        }
394
395        if (o.posChildNeedsLayout()) {
396            if (havePrevious)
397                ts << ",";
398            ts << " positioned child";
399        }
400
401        if (needsLayout)
402            ts << ")";
403    }
404}
405
406static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run)
407{
408    // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder
409    // to detect any changes caused by the conversion to floating point. :(
410    int x = run.x();
411    int y = run.y();
412    int logicalWidth = ceilf(run.left() + run.logicalWidth()) - x;
413
414    // FIXME: Table cell adjustment is temporary until results can be updated.
415    if (o.containingBlock()->isTableCell())
416        y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore();
417
418    ts << "text run at (" << x << "," << y << ") width " << logicalWidth;
419    if (!run.isLeftToRightDirection() || run.dirOverride()) {
420        ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR");
421        if (run.dirOverride())
422            ts << " override";
423    }
424    ts << ": "
425        << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len()));
426    if (run.hasHyphen())
427        ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style()->hyphenString());
428    ts << "\n";
429}
430
431void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior)
432{
433    if (o.isSVGShape()) {
434        write(ts, toRenderSVGShape(o), indent);
435        return;
436    }
437    if (o.isSVGGradientStop()) {
438        writeSVGGradientStop(ts, toRenderSVGGradientStop(o), indent);
439        return;
440    }
441    if (o.isSVGResourceContainer()) {
442        writeSVGResourceContainer(ts, o, indent);
443        return;
444    }
445    if (o.isSVGContainer()) {
446        writeSVGContainer(ts, o, indent);
447        return;
448    }
449    if (o.isSVGRoot()) {
450        write(ts, toRenderSVGRoot(o), indent);
451        return;
452    }
453    if (o.isSVGText()) {
454        writeSVGText(ts, toRenderSVGText(o), indent);
455        return;
456    }
457    if (o.isSVGInlineText()) {
458        writeSVGInlineText(ts, toRenderSVGInlineText(o), indent);
459        return;
460    }
461    if (o.isSVGImage()) {
462        writeSVGImage(ts, toRenderSVGImage(o), indent);
463        return;
464    }
465
466    writeIndent(ts, indent);
467
468    RenderTreeAsText::writeRenderObject(ts, o, behavior);
469    ts << "\n";
470
471    if (o.isText() && !o.isBR()) {
472        const RenderText& text = toRenderText(o);
473        for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
474            writeIndent(ts, indent + 1);
475            writeTextRun(ts, text, *box);
476        }
477    }
478
479    for (RenderObject* child = o.slowFirstChild(); child; child = child->nextSibling()) {
480        if (child->hasLayer())
481            continue;
482        write(ts, *child, indent + 1, behavior);
483    }
484
485    if (o.isWidget()) {
486        Widget* widget = toRenderWidget(o).widget();
487        if (widget && widget->isFrameView()) {
488            FrameView* view = toFrameView(widget);
489            RenderView* root = view->renderView();
490            if (root) {
491                view->layout();
492                RenderLayer* l = root->layer();
493                if (l)
494                    RenderTreeAsText::writeLayers(ts, l, l, l->rect(), indent + 1, behavior);
495            }
496        }
497    }
498}
499
500enum LayerPaintPhase {
501    LayerPaintPhaseAll = 0,
502    LayerPaintPhaseBackground = -1,
503    LayerPaintPhaseForeground = 1
504};
505
506static void write(TextStream& ts, RenderLayer& l,
507                  const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, const LayoutRect& clipRect, const LayoutRect& outlineClipRect,
508                  LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal)
509{
510    IntRect adjustedLayoutBounds = pixelSnappedIntRect(layerBounds);
511    IntRect adjustedBackgroundClipRect = pixelSnappedIntRect(backgroundClipRect);
512    IntRect adjustedClipRect = pixelSnappedIntRect(clipRect);
513    IntRect adjustedOutlineClipRect = pixelSnappedIntRect(outlineClipRect);
514
515    writeIndent(ts, indent);
516
517    if (l.renderer()->style()->visibility() == HIDDEN)
518        ts << "hidden ";
519
520    ts << "layer ";
521
522    if (behavior & RenderAsTextShowAddresses)
523        ts << static_cast<const void*>(&l) << " ";
524
525    ts << adjustedLayoutBounds;
526
527    if (!adjustedLayoutBounds.isEmpty()) {
528        if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds))
529            ts << " backgroundClip " << adjustedBackgroundClipRect;
530        if (!adjustedClipRect.contains(adjustedLayoutBounds))
531            ts << " clip " << adjustedClipRect;
532        if (!adjustedOutlineClipRect.contains(adjustedLayoutBounds))
533            ts << " outlineClip " << adjustedOutlineClipRect;
534    }
535    if (l.isTransparent())
536        ts << " transparent";
537
538    if (l.renderer()->hasOverflowClip()) {
539        if (l.scrollableArea()->scrollXOffset())
540            ts << " scrollX " << l.scrollableArea()->scrollXOffset();
541        if (l.scrollableArea()->scrollYOffset())
542            ts << " scrollY " << l.scrollableArea()->scrollYOffset();
543        if (l.renderBox() && l.renderBox()->pixelSnappedClientWidth() != l.renderBox()->pixelSnappedScrollWidth())
544            ts << " scrollWidth " << l.renderBox()->pixelSnappedScrollWidth();
545        if (l.renderBox() && l.renderBox()->pixelSnappedClientHeight() != l.renderBox()->pixelSnappedScrollHeight())
546            ts << " scrollHeight " << l.renderBox()->pixelSnappedScrollHeight();
547    }
548
549    if (paintPhase == LayerPaintPhaseBackground)
550        ts << " layerType: background only";
551    else if (paintPhase == LayerPaintPhaseForeground)
552        ts << " layerType: foreground only";
553
554    if (l.renderer()->hasBlendMode())
555        ts << " blendMode: " << compositeOperatorName(CompositeSourceOver, l.renderer()->style()->blendMode());
556
557    if (behavior & RenderAsTextShowCompositedLayers) {
558        if (l.hasCompositedLayerMapping()) {
559            ts << " (composited, bounds="
560                << l.compositedLayerMapping()->compositedBounds()
561                << ", drawsContent="
562                << l.compositedLayerMapping()->mainGraphicsLayer()->drawsContent()
563                << ", paints into ancestor="
564                << l.compositedLayerMapping()->paintsIntoCompositedAncestor()
565                << (l.shouldIsolateCompositedDescendants() ? ", isolatesCompositedBlending" : "")
566                << ")";
567        }
568    }
569
570    ts << "\n";
571
572    if (paintPhase != LayerPaintPhaseBackground)
573        write(ts, *l.renderer(), indent + 1, behavior);
574}
575
576void RenderTreeAsText::writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* layer,
577    const LayoutRect& paintRect, int indent, RenderAsTextBehavior behavior)
578{
579    // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh.
580    LayoutRect paintDirtyRect(paintRect);
581    if (rootLayer == layer) {
582        paintDirtyRect.setWidth(max<LayoutUnit>(paintDirtyRect.width(), rootLayer->renderBox()->layoutOverflowRect().maxX()));
583        paintDirtyRect.setHeight(max<LayoutUnit>(paintDirtyRect.height(), rootLayer->renderBox()->layoutOverflowRect().maxY()));
584    }
585
586    // Calculate the clip rects we should use.
587    LayoutRect layerBounds;
588    ClipRect damageRect, clipRectToApply, outlineRect;
589    layer->clipper().calculateRects(ClipRectsContext(rootLayer, UncachedClipRects), paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect);
590
591    // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh.
592    if (rootLayer == layer)
593        layerBounds.setSize(layer->size().expandedTo(pixelSnappedIntSize(layer->renderBox()->maxLayoutOverflow(), LayoutPoint(0, 0))));
594
595    // Ensure our lists are up-to-date.
596    layer->stackingNode()->updateLayerListsIfNeeded();
597
598    bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : layer->intersectsDamageRect(layerBounds, damageRect.rect(), rootLayer);
599
600    Vector<RenderLayerStackingNode*>* negList = layer->stackingNode()->negZOrderList();
601    bool paintsBackgroundSeparately = negList && negList->size() > 0;
602    if (shouldPaint && paintsBackgroundSeparately)
603        write(ts, *layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), LayerPaintPhaseBackground, indent, behavior);
604
605    if (negList) {
606        int currIndent = indent;
607        if (behavior & RenderAsTextShowLayerNesting) {
608            writeIndent(ts, indent);
609            ts << " negative z-order list(" << negList->size() << ")\n";
610            ++currIndent;
611        }
612        for (unsigned i = 0; i != negList->size(); ++i)
613            writeLayers(ts, rootLayer, negList->at(i)->layer(), paintDirtyRect, currIndent, behavior);
614    }
615
616    if (shouldPaint)
617        write(ts, *layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior);
618
619    if (Vector<RenderLayerStackingNode*>* normalFlowList = layer->stackingNode()->normalFlowList()) {
620        int currIndent = indent;
621        if (behavior & RenderAsTextShowLayerNesting) {
622            writeIndent(ts, indent);
623            ts << " normal flow list(" << normalFlowList->size() << ")\n";
624            ++currIndent;
625        }
626        for (unsigned i = 0; i != normalFlowList->size(); ++i)
627            writeLayers(ts, rootLayer, normalFlowList->at(i)->layer(), paintDirtyRect, currIndent, behavior);
628    }
629
630    if (Vector<RenderLayerStackingNode*>* posList = layer->stackingNode()->posZOrderList()) {
631        int currIndent = indent;
632        if (behavior & RenderAsTextShowLayerNesting) {
633            writeIndent(ts, indent);
634            ts << " positive z-order list(" << posList->size() << ")\n";
635            ++currIndent;
636        }
637        for (unsigned i = 0; i != posList->size(); ++i)
638            writeLayers(ts, rootLayer, posList->at(i)->layer(), paintDirtyRect, currIndent, behavior);
639    }
640}
641
642String nodePositionAsStringForTesting(Node* node)
643{
644    StringBuilder result;
645
646    Element* body = node->document().body();
647    Node* parent;
648    for (Node* n = node; n; n = parent) {
649        parent = n->parentOrShadowHostNode();
650        if (n != node)
651            result.appendLiteral(" of ");
652        if (parent) {
653            if (body && n == body) {
654                // We don't care what offset body may be in the document.
655                result.appendLiteral("body");
656                break;
657            }
658            if (n->isShadowRoot()) {
659                result.append('{');
660                result.append(getTagName(n));
661                result.append('}');
662            } else {
663                result.appendLiteral("child ");
664                result.appendNumber(n->nodeIndex());
665                result.appendLiteral(" {");
666                result.append(getTagName(n));
667                result.append('}');
668            }
669        } else
670            result.appendLiteral("document");
671    }
672
673    return result.toString();
674}
675
676static void writeSelection(TextStream& ts, const RenderObject* o)
677{
678    Node* n = o->node();
679    if (!n || !n->isDocumentNode())
680        return;
681
682    Document* doc = toDocument(n);
683    LocalFrame* frame = doc->frame();
684    if (!frame)
685        return;
686
687    VisibleSelection selection = frame->selection().selection();
688    if (selection.isCaret()) {
689        ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePositionAsStringForTesting(selection.start().deprecatedNode());
690        if (selection.affinity() == UPSTREAM)
691            ts << " (upstream affinity)";
692        ts << "\n";
693    } else if (selection.isRange()) {
694        ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePositionAsStringForTesting(selection.start().deprecatedNode()) << "\n"
695            << "selection end:   position " << selection.end().deprecatedEditingOffset() << " of " << nodePositionAsStringForTesting(selection.end().deprecatedNode()) << "\n";
696    }
697}
698
699static String externalRepresentation(RenderBox* renderer, RenderAsTextBehavior behavior)
700{
701    TextStream ts;
702    if (!renderer->hasLayer())
703        return ts.release();
704
705    RenderLayer* layer = renderer->layer();
706    RenderTreeAsText::writeLayers(ts, layer, layer, layer->rect(), 0, behavior);
707    writeSelection(ts, renderer);
708    return ts.release();
709}
710
711String externalRepresentation(LocalFrame* frame, RenderAsTextBehavior behavior)
712{
713    if (!(behavior & RenderAsTextDontUpdateLayout))
714        frame->document()->updateLayout();
715
716    RenderObject* renderer = frame->contentRenderer();
717    if (!renderer || !renderer->isBox())
718        return String();
719
720    PrintContext printContext(frame);
721    if (behavior & RenderAsTextPrintingMode)
722        printContext.begin(toRenderBox(renderer)->width().toFloat());
723
724    return externalRepresentation(toRenderBox(renderer), behavior);
725}
726
727String externalRepresentation(Element* element, RenderAsTextBehavior behavior)
728{
729    // Doesn't support printing mode.
730    ASSERT(!(behavior & RenderAsTextPrintingMode));
731    if (!(behavior & RenderAsTextDontUpdateLayout))
732        element->document().updateLayout();
733
734    RenderObject* renderer = element->renderer();
735    if (!renderer || !renderer->isBox())
736        return String();
737
738    return externalRepresentation(toRenderBox(renderer), behavior | RenderAsTextShowAllLayers);
739}
740
741static void writeCounterValuesFromChildren(TextStream& stream, RenderObject* parent, bool& isFirstCounter)
742{
743    for (RenderObject* child = parent->slowFirstChild(); child; child = child->nextSibling()) {
744        if (child->isCounter()) {
745            if (!isFirstCounter)
746                stream << " ";
747            isFirstCounter = false;
748            String str(toRenderText(child)->text());
749            stream << str;
750        }
751    }
752}
753
754String counterValueForElement(Element* element)
755{
756    // Make sure the element is not freed during the layout.
757    RefPtrWillBeRawPtr<Element> protector(element);
758    element->document().updateLayout();
759    TextStream stream;
760    bool isFirstCounter = true;
761    // The counter renderers should be children of :before or :after pseudo-elements.
762    if (RenderObject* before = element->pseudoElementRenderer(BEFORE))
763        writeCounterValuesFromChildren(stream, before, isFirstCounter);
764    if (RenderObject* after = element->pseudoElementRenderer(AFTER))
765        writeCounterValuesFromChildren(stream, after, isFirstCounter);
766    return stream.release();
767}
768
769String markerTextForListItem(Element* element)
770{
771    // Make sure the element is not freed during the layout.
772    RefPtrWillBeRawPtr<Element> protector(element);
773    element->document().updateLayout();
774
775    RenderObject* renderer = element->renderer();
776    if (!renderer || !renderer->isListItem())
777        return String();
778
779    return toRenderListItem(renderer)->markerText();
780}
781
782} // namespace blink
783