Home | History | Annotate | Download | only in rendering
      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 "CSSMutableStyleDeclaration.h"
     30 #include "CharacterNames.h"
     31 #include "CString.h"
     32 #include "Document.h"
     33 #include "Frame.h"
     34 #include "FrameView.h"
     35 #include "HTMLElement.h"
     36 #include "HTMLNames.h"
     37 #include "InlineTextBox.h"
     38 #include "RenderBR.h"
     39 #include "RenderFileUploadControl.h"
     40 #include "RenderInline.h"
     41 #include "RenderListMarker.h"
     42 #include "RenderPart.h"
     43 #include "RenderTableCell.h"
     44 #include "RenderView.h"
     45 #include "RenderWidget.h"
     46 #include "SelectionController.h"
     47 #include "TextStream.h"
     48 #include <wtf/UnusedParam.h>
     49 #include <wtf/Vector.h>
     50 
     51 #if ENABLE(SVG)
     52 #include "RenderPath.h"
     53 #include "RenderSVGContainer.h"
     54 #include "RenderSVGImage.h"
     55 #include "RenderSVGInlineText.h"
     56 #include "RenderSVGRoot.h"
     57 #include "RenderSVGText.h"
     58 #include "SVGRenderTreeAsText.h"
     59 #endif
     60 
     61 #if USE(ACCELERATED_COMPOSITING)
     62 #include "RenderLayerBacking.h"
     63 #endif
     64 
     65 #if PLATFORM(QT)
     66 #include <QWidget>
     67 #endif
     68 
     69 namespace WebCore {
     70 
     71 using namespace HTMLNames;
     72 
     73 static void writeLayers(TextStream&, const RenderLayer* rootLayer, RenderLayer*, const IntRect& paintDirtyRect, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal);
     74 
     75 #if !ENABLE(SVG)
     76 static TextStream &operator<<(TextStream& ts, const IntRect& r)
     77 {
     78     return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height();
     79 }
     80 #endif
     81 
     82 static void writeIndent(TextStream& ts, int indent)
     83 {
     84     for (int i = 0; i != indent; ++i)
     85         ts << "  ";
     86 }
     87 
     88 static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle)
     89 {
     90     switch (borderStyle) {
     91         case BNONE:
     92             ts << "none";
     93             break;
     94         case BHIDDEN:
     95             ts << "hidden";
     96             break;
     97         case INSET:
     98             ts << "inset";
     99             break;
    100         case GROOVE:
    101             ts << "groove";
    102             break;
    103         case RIDGE:
    104             ts << "ridge";
    105             break;
    106         case OUTSET:
    107             ts << "outset";
    108             break;
    109         case DOTTED:
    110             ts << "dotted";
    111             break;
    112         case DASHED:
    113             ts << "dashed";
    114             break;
    115         case SOLID:
    116             ts << "solid";
    117             break;
    118         case DOUBLE:
    119             ts << "double";
    120             break;
    121     }
    122 
    123     ts << " ";
    124 }
    125 
    126 static String getTagName(Node* n)
    127 {
    128     if (n->isDocumentNode())
    129         return "";
    130     if (n->isCommentNode())
    131         return "COMMENT";
    132     return n->nodeName();
    133 }
    134 
    135 static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
    136 {
    137     if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
    138         return false;
    139 
    140     const HTMLElement* elem = static_cast<const HTMLElement*>(node);
    141     if (elem->getAttribute(classAttr) != "Apple-style-span")
    142         return false;
    143 
    144     if (!node->hasChildNodes())
    145         return true;
    146 
    147     CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
    148     return (!inlineStyleDecl || inlineStyleDecl->length() == 0);
    149 }
    150 
    151 String quoteAndEscapeNonPrintables(const String& s)
    152 {
    153     Vector<UChar> result;
    154     result.append('"');
    155     for (unsigned i = 0; i != s.length(); ++i) {
    156         UChar c = s[i];
    157         if (c == '\\') {
    158             result.append('\\');
    159             result.append('\\');
    160         } else if (c == '"') {
    161             result.append('\\');
    162             result.append('"');
    163         } else if (c == '\n' || c == noBreakSpace)
    164             result.append(' ');
    165         else {
    166             if (c >= 0x20 && c < 0x7F)
    167                 result.append(c);
    168             else {
    169                 unsigned u = c;
    170                 String hex = String::format("\\x{%X}", u);
    171                 unsigned len = hex.length();
    172                 for (unsigned i = 0; i < len; ++i)
    173                     result.append(hex[i]);
    174             }
    175         }
    176     }
    177     result.append('"');
    178     return String::adopt(result);
    179 }
    180 
    181 static TextStream &operator<<(TextStream& ts, const RenderObject& o)
    182 {
    183     ts << o.renderName();
    184 
    185     if (o.style() && o.style()->zIndex())
    186         ts << " zI: " << o.style()->zIndex();
    187 
    188     if (o.node()) {
    189         String tagName = getTagName(o.node());
    190         if (!tagName.isEmpty()) {
    191             ts << " {" << tagName << "}";
    192             // flag empty or unstyled AppleStyleSpan because we never
    193             // want to leave them in the DOM
    194             if (isEmptyOrUnstyledAppleStyleSpan(o.node()))
    195                 ts << " *empty or unstyled AppleStyleSpan*";
    196         }
    197     }
    198 
    199     bool adjustForTableCells = o.containingBlock()->isTableCell();
    200 
    201     IntRect r;
    202     if (o.isText()) {
    203         // 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
    204         // many test results.
    205         const RenderText& text = *toRenderText(&o);
    206         IntRect linesBox = text.linesBoundingBox();
    207         r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height());
    208         if (adjustForTableCells && !text.firstTextBox())
    209             adjustForTableCells = false;
    210     } else if (o.isRenderInline()) {
    211         // FIXME: Would be better not to just dump 0, 0 as the x and y here.
    212         const RenderInline& inlineFlow = *toRenderInline(&o);
    213         r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height());
    214         adjustForTableCells = false;
    215     } else if (o.isTableCell()) {
    216         // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect.  We'd like
    217         // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are
    218         // captured by the results.
    219         const RenderTableCell& cell = *toRenderTableCell(&o);
    220         r = IntRect(cell.x(), cell.y() + cell.intrinsicPaddingTop(), cell.width(), cell.height() - cell.intrinsicPaddingTop() - cell.intrinsicPaddingBottom());
    221     } else if (o.isBox())
    222         r = toRenderBox(&o)->frameRect();
    223 
    224     // FIXME: Temporary in order to ensure compatibility with existing layout test results.
    225     if (adjustForTableCells)
    226         r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingTop());
    227 
    228     ts << " " << r;
    229 
    230     if (!(o.isText() && !o.isBR())) {
    231         if (o.isFileUploadControl()) {
    232             ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue());
    233         }
    234         if (o.parent() && (o.parent()->style()->color() != o.style()->color()))
    235             ts << " [color=" << o.style()->color().name() << "]";
    236 
    237         if (o.parent() && (o.parent()->style()->backgroundColor() != o.style()->backgroundColor()) &&
    238             o.style()->backgroundColor().isValid() && o.style()->backgroundColor().rgb())
    239             // Do not dump invalid or transparent backgrounds, since that is the default.
    240             ts << " [bgcolor=" << o.style()->backgroundColor().name() << "]";
    241 
    242         if (o.parent() && (o.parent()->style()->textFillColor() != o.style()->textFillColor()) &&
    243             o.style()->textFillColor().isValid() && o.style()->textFillColor() != o.style()->color() &&
    244             o.style()->textFillColor().rgb())
    245             ts << " [textFillColor=" << o.style()->textFillColor().name() << "]";
    246 
    247         if (o.parent() && (o.parent()->style()->textStrokeColor() != o.style()->textStrokeColor()) &&
    248             o.style()->textStrokeColor().isValid() && o.style()->textStrokeColor() != o.style()->color() &&
    249             o.style()->textStrokeColor().rgb())
    250             ts << " [textStrokeColor=" << o.style()->textStrokeColor().name() << "]";
    251 
    252         if (o.parent() && (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth()) &&
    253             o.style()->textStrokeWidth() > 0)
    254             ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]";
    255 
    256         if (!o.isBoxModelObject())
    257             return ts;
    258 
    259         const RenderBoxModelObject& box = *toRenderBoxModelObject(&o);
    260         if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) {
    261             ts << " [border:";
    262 
    263             BorderValue prevBorder;
    264             if (o.style()->borderTop() != prevBorder) {
    265                 prevBorder = o.style()->borderTop();
    266                 if (!box.borderTop())
    267                     ts << " none";
    268                 else {
    269                     ts << " (" << box.borderTop() << "px ";
    270                     printBorderStyle(ts, o.style()->borderTopStyle());
    271                     Color col = o.style()->borderTopColor();
    272                     if (!col.isValid())
    273                         col = o.style()->color();
    274                     ts << col.name() << ")";
    275                 }
    276             }
    277 
    278             if (o.style()->borderRight() != prevBorder) {
    279                 prevBorder = o.style()->borderRight();
    280                 if (!box.borderRight())
    281                     ts << " none";
    282                 else {
    283                     ts << " (" << box.borderRight() << "px ";
    284                     printBorderStyle(ts, o.style()->borderRightStyle());
    285                     Color col = o.style()->borderRightColor();
    286                     if (!col.isValid())
    287                         col = o.style()->color();
    288                     ts << col.name() << ")";
    289                 }
    290             }
    291 
    292             if (o.style()->borderBottom() != prevBorder) {
    293                 prevBorder = box.style()->borderBottom();
    294                 if (!box.borderBottom())
    295                     ts << " none";
    296                 else {
    297                     ts << " (" << box.borderBottom() << "px ";
    298                     printBorderStyle(ts, o.style()->borderBottomStyle());
    299                     Color col = o.style()->borderBottomColor();
    300                     if (!col.isValid())
    301                         col = o.style()->color();
    302                     ts << col.name() << ")";
    303                 }
    304             }
    305 
    306             if (o.style()->borderLeft() != prevBorder) {
    307                 prevBorder = o.style()->borderLeft();
    308                 if (!box.borderLeft())
    309                     ts << " none";
    310                 else {
    311                     ts << " (" << box.borderLeft() << "px ";
    312                     printBorderStyle(ts, o.style()->borderLeftStyle());
    313                     Color col = o.style()->borderLeftColor();
    314                     if (!col.isValid())
    315                         col = o.style()->color();
    316                     ts << col.name() << ")";
    317                 }
    318             }
    319 
    320             ts << "]";
    321         }
    322     }
    323 
    324     if (o.isTableCell()) {
    325         const RenderTableCell& c = *toRenderTableCell(&o);
    326         ts << " [r=" << c.row() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
    327     }
    328 
    329     if (o.isListMarker()) {
    330         String text = toRenderListMarker(&o)->text();
    331         if (!text.isEmpty()) {
    332             if (text.length() != 1)
    333                 text = quoteAndEscapeNonPrintables(text);
    334             else {
    335                 switch (text[0]) {
    336                     case bullet:
    337                         text = "bullet";
    338                         break;
    339                     case blackSquare:
    340                         text = "black square";
    341                         break;
    342                     case whiteBullet:
    343                         text = "white bullet";
    344                         break;
    345                     default:
    346                         text = quoteAndEscapeNonPrintables(text);
    347                 }
    348             }
    349             ts << ": " << text;
    350         }
    351     }
    352 
    353 #if PLATFORM(QT)
    354     // Print attributes of embedded QWidgets. E.g. when the WebCore::Widget
    355     // is invisible the QWidget should be invisible too.
    356     if (o.isRenderPart()) {
    357         const RenderPart* part = toRenderPart(const_cast<RenderObject*>(&o));
    358         if (part->widget() && part->widget()->platformWidget()) {
    359             QWidget* wid = part->widget()->platformWidget();
    360 
    361             ts << " [QT: ";
    362             ts << "geometry: {" << wid->geometry() << "} ";
    363             ts << "isHidden: " << wid->isHidden() << " ";
    364             ts << "isSelfVisible: " << part->widget()->isSelfVisible() << " ";
    365             ts << "isParentVisible: " << part->widget()->isParentVisible() << " ";
    366             ts << "mask: {" << wid->mask().boundingRect() << "} ] ";
    367         }
    368     }
    369 #endif
    370 
    371     return ts;
    372 }
    373 
    374 static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run)
    375 {
    376     // FIXME: Table cell adjustment is temporary until results can be updated.
    377     int y = run.m_y;
    378     if (o.containingBlock()->isTableCell())
    379         y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingTop();
    380     ts << "text run at (" << run.m_x << "," << y << ") width " << run.m_width;
    381     if (run.direction() == RTL || run.m_dirOverride) {
    382         ts << (run.direction() == RTL ? " RTL" : " LTR");
    383         if (run.m_dirOverride)
    384             ts << " override";
    385     }
    386     ts << ": "
    387         << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len()))
    388         << "\n";
    389 }
    390 
    391 void write(TextStream& ts, const RenderObject& o, int indent)
    392 {
    393 #if ENABLE(SVG)
    394     if (o.isRenderPath()) {
    395         write(ts, *toRenderPath(&o), indent);
    396         return;
    397     }
    398     if (o.isSVGContainer()) {
    399         writeSVGContainer(ts, o, indent);
    400         return;
    401     }
    402     if (o.isSVGRoot()) {
    403         write(ts, *toRenderSVGRoot(&o), indent);
    404         return;
    405     }
    406     if (o.isSVGText()) {
    407         if (!o.isText())
    408             writeSVGText(ts, *toRenderBlock(&o), indent);
    409         else
    410             writeSVGInlineText(ts, *toRenderText(&o), indent);
    411         return;
    412     }
    413     if (o.isSVGImage()) {
    414         writeSVGImage(ts, *toRenderImage(&o), indent);
    415         return;
    416     }
    417 #endif
    418 
    419     writeIndent(ts, indent);
    420 
    421     ts << o << "\n";
    422 
    423     if (o.isText() && !o.isBR()) {
    424         const RenderText& text = *toRenderText(&o);
    425         for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
    426             writeIndent(ts, indent + 1);
    427             writeTextRun(ts, text, *box);
    428         }
    429     }
    430 
    431     for (RenderObject* child = o.firstChild(); child; child = child->nextSibling()) {
    432         if (child->hasLayer())
    433             continue;
    434         write(ts, *child, indent + 1);
    435     }
    436 
    437     if (o.isWidget()) {
    438         Widget* widget = toRenderWidget(&o)->widget();
    439         if (widget && widget->isFrameView()) {
    440             FrameView* view = static_cast<FrameView*>(widget);
    441             RenderView* root = view->frame()->contentRenderer();
    442             if (root) {
    443                 view->layout();
    444                 RenderLayer* l = root->layer();
    445                 if (l)
    446                     writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), indent + 1);
    447             }
    448         }
    449     }
    450 }
    451 
    452 enum LayerPaintPhase {
    453     LayerPaintPhaseAll = 0,
    454     LayerPaintPhaseBackground = -1,
    455     LayerPaintPhaseForeground = 1
    456 };
    457 
    458 static void write(TextStream& ts, RenderLayer& l,
    459                   const IntRect& layerBounds, const IntRect& backgroundClipRect, const IntRect& clipRect, const IntRect& outlineClipRect,
    460                   LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal)
    461 {
    462     writeIndent(ts, indent);
    463 
    464     ts << "layer " << layerBounds;
    465 
    466     if (!layerBounds.isEmpty()) {
    467         if (!backgroundClipRect.contains(layerBounds))
    468             ts << " backgroundClip " << backgroundClipRect;
    469         if (!clipRect.contains(layerBounds))
    470             ts << " clip " << clipRect;
    471         if (!outlineClipRect.contains(layerBounds))
    472             ts << " outlineClip " << outlineClipRect;
    473     }
    474 
    475     if (l.renderer()->hasOverflowClip()) {
    476         if (l.scrollXOffset())
    477             ts << " scrollX " << l.scrollXOffset();
    478         if (l.scrollYOffset())
    479             ts << " scrollY " << l.scrollYOffset();
    480         if (l.renderBox() && l.renderBox()->clientWidth() != l.scrollWidth())
    481             ts << " scrollWidth " << l.scrollWidth();
    482         if (l.renderBox() && l.renderBox()->clientHeight() != l.scrollHeight())
    483             ts << " scrollHeight " << l.scrollHeight();
    484     }
    485 
    486     if (paintPhase == LayerPaintPhaseBackground)
    487         ts << " layerType: background only";
    488     else if (paintPhase == LayerPaintPhaseForeground)
    489         ts << " layerType: foreground only";
    490 
    491 #if USE(ACCELERATED_COMPOSITING)
    492     if (behavior & RenderAsTextShowCompositedLayers) {
    493         if (l.isComposited())
    494             ts << " (composited, bounds " << l.backing()->compositedBounds() << ")";
    495     }
    496 #else
    497     UNUSED_PARAM(behavior);
    498 #endif
    499 
    500     ts << "\n";
    501 
    502     if (paintPhase != LayerPaintPhaseBackground)
    503         write(ts, *l.renderer(), indent + 1);
    504 }
    505 
    506 static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l,
    507                         const IntRect& paintDirtyRect, int indent, RenderAsTextBehavior behavior)
    508 {
    509     // Calculate the clip rects we should use.
    510     IntRect layerBounds, damageRect, clipRectToApply, outlineRect;
    511     l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, true);
    512 
    513     // Ensure our lists are up-to-date.
    514     l->updateZOrderLists();
    515     l->updateNormalFlowList();
    516 
    517     bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : l->intersectsDamageRect(layerBounds, damageRect, rootLayer);
    518     Vector<RenderLayer*>* negList = l->negZOrderList();
    519     bool paintsBackgroundSeparately = negList && negList->size() > 0;
    520     if (shouldPaint && paintsBackgroundSeparately)
    521         write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, LayerPaintPhaseBackground, indent, behavior);
    522 
    523     if (negList) {
    524         int currIndent = indent;
    525         if (behavior & RenderAsTextShowLayerNesting) {
    526             writeIndent(ts, indent);
    527             ts << " negative z-order list(" << negList->size() << ")\n";
    528             ++currIndent;
    529         }
    530         for (unsigned i = 0; i != negList->size(); ++i)
    531             writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, currIndent, behavior);
    532     }
    533 
    534     if (shouldPaint)
    535         write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior);
    536 
    537     if (Vector<RenderLayer*>* normalFlowList = l->normalFlowList()) {
    538         int currIndent = indent;
    539         if (behavior & RenderAsTextShowLayerNesting) {
    540             writeIndent(ts, indent);
    541             ts << " normal flow list(" << normalFlowList->size() << ")\n";
    542             ++currIndent;
    543         }
    544         for (unsigned i = 0; i != normalFlowList->size(); ++i)
    545             writeLayers(ts, rootLayer, normalFlowList->at(i), paintDirtyRect, currIndent, behavior);
    546     }
    547 
    548     if (Vector<RenderLayer*>* posList = l->posZOrderList()) {
    549         int currIndent = indent;
    550         if (behavior & RenderAsTextShowLayerNesting) {
    551             writeIndent(ts, indent);
    552             ts << " positive z-order list(" << posList->size() << ")\n";
    553             ++currIndent;
    554         }
    555         for (unsigned i = 0; i != posList->size(); ++i)
    556             writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, currIndent, behavior);
    557     }
    558 }
    559 
    560 static String nodePosition(Node* node)
    561 {
    562     String result;
    563 
    564     Node* parent;
    565     for (Node* n = node; n; n = parent) {
    566         parent = n->parentNode();
    567         if (!parent)
    568             parent = n->shadowParentNode();
    569         if (n != node)
    570             result += " of ";
    571         if (parent)
    572             result += "child " + String::number(n->nodeIndex()) + " {" + getTagName(n) + "}";
    573         else
    574             result += "document";
    575     }
    576 
    577     return result;
    578 }
    579 
    580 static void writeSelection(TextStream& ts, const RenderObject* o)
    581 {
    582     Node* n = o->node();
    583     if (!n || !n->isDocumentNode())
    584         return;
    585 
    586     Document* doc = static_cast<Document*>(n);
    587     Frame* frame = doc->frame();
    588     if (!frame)
    589         return;
    590 
    591     VisibleSelection selection = frame->selection()->selection();
    592     if (selection.isCaret()) {
    593         ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node());
    594         if (selection.affinity() == UPSTREAM)
    595             ts << " (upstream affinity)";
    596         ts << "\n";
    597     } else if (selection.isRange())
    598         ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node()) << "\n"
    599            << "selection end:   position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().node()) << "\n";
    600 }
    601 
    602 String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior)
    603 {
    604     frame->document()->updateLayout();
    605 
    606     RenderObject* o = frame->contentRenderer();
    607     if (!o)
    608         return String();
    609 
    610     TextStream ts;
    611 #if ENABLE(SVG)
    612     writeRenderResources(ts, o->document());
    613 #endif
    614     if (o->hasLayer()) {
    615         RenderLayer* l = toRenderBox(o)->layer();
    616         writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), 0, behavior);
    617         writeSelection(ts, o);
    618     }
    619     return ts.release();
    620 }
    621 
    622 static void writeCounterValuesFromChildren(TextStream& stream, RenderObject* parent, bool& isFirstCounter)
    623 {
    624     for (RenderObject* child = parent->firstChild(); child; child = child->nextSibling()) {
    625         if (child->isCounter()) {
    626             if (!isFirstCounter)
    627                 stream << " ";
    628             isFirstCounter = false;
    629             String str(toRenderText(child)->text());
    630             stream << str;
    631         }
    632     }
    633 }
    634 
    635 String counterValueForElement(Element* element)
    636 {
    637     // Make sure the element is not freed during the layout.
    638     RefPtr<Element> elementRef(element);
    639     element->document()->updateLayout();
    640     TextStream stream;
    641     bool isFirstCounter = true;
    642     // The counter renderers should be children of anonymous children
    643     // (i.e., :before or :after pseudo-elements).
    644     if (RenderObject* renderer = element->renderer()) {
    645         for (RenderObject* child = renderer->firstChild(); child; child = child->nextSibling()) {
    646             if (child->isAnonymous())
    647                 writeCounterValuesFromChildren(stream, child, isFirstCounter);
    648         }
    649     }
    650     return stream.release();
    651 }
    652 
    653 } // namespace WebCore
    654