WebCore/rendering/RenderTreeAsText.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/rendering/RenderTreeAsText.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "RenderTreeAsText.h"
+
+#include "CSSMutableStyleDeclaration.h"
+#include "CharacterNames.h"
+#include "Document.h"
+#include "Frame.h"
+#include "FrameView.h"
+#include "HTMLElement.h"
+#include "HTMLNames.h"
+#include "InlineTextBox.h"
+#include "PrintContext.h"
+#include "RenderBR.h"
+#include "RenderFileUploadControl.h"
+#include "RenderInline.h"
+#include "RenderLayer.h"
+#include "RenderListItem.h"
+#include "RenderListMarker.h"
+#include "RenderPart.h"
+#include "RenderTableCell.h"
+#include "RenderView.h"
+#include "RenderWidget.h"
+#include "SelectionController.h"
+#include "TextStream.h"
+#include <wtf/UnusedParam.h>
+#include <wtf/Vector.h>
+
+#if ENABLE(SVG)
+#include "RenderPath.h"
+#include "RenderSVGContainer.h"
+#include "RenderSVGGradientStop.h"
+#include "RenderSVGImage.h"
+#include "RenderSVGInlineText.h"
+#include "RenderSVGRoot.h"
+#include "RenderSVGText.h"
+#include "SVGRenderTreeAsText.h"
+#endif
+
+#if USE(ACCELERATED_COMPOSITING)
+#include "RenderLayerBacking.h"
+#endif
+
+#if PLATFORM(QT)
+#include <QWidget>
+#endif
+
+namespace WebCore {
+
+using namespace HTMLNames;
+
+static void writeLayers(TextStream&, const RenderLayer* rootLayer, RenderLayer*, const IntRect& paintDirtyRect, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal);
+
+#if !ENABLE(SVG)
+static TextStream &operator<<(TextStream& ts, const IntRect& r)
+{
+    return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height();
+}
+#endif
+
+void writeIndent(TextStream& ts, int indent)
+{
+    for (int i = 0; i != indent; ++i)
+        ts << "  ";
+}
+
+static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle)
+{
+    switch (borderStyle) {
+        case BNONE:
+            ts << "none";
+            break;
+        case BHIDDEN:
+            ts << "hidden";
+            break;
+        case INSET:
+            ts << "inset";
+            break;
+        case GROOVE:
+            ts << "groove";
+            break;
+        case RIDGE:
+            ts << "ridge";
+            break;
+        case OUTSET:
+            ts << "outset";
+            break;
+        case DOTTED:
+            ts << "dotted";
+            break;
+        case DASHED:
+            ts << "dashed";
+            break;
+        case SOLID:
+            ts << "solid";
+            break;
+        case DOUBLE:
+            ts << "double";
+            break;
+    }
+
+    ts << " ";
+}
+
+static String getTagName(Node* n)
+{
+    if (n->isDocumentNode())
+        return "";
+    if (n->isCommentNode())
+        return "COMMENT";
+    return n->nodeName();
+}
+
+static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
+{
+    if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
+        return false;
+
+    const HTMLElement* elem = static_cast<const HTMLElement*>(node);
+    if (elem->getAttribute(classAttr) != "Apple-style-span")
+        return false;
+
+    if (!node->hasChildNodes())
+        return true;
+
+    CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
+    return (!inlineStyleDecl || inlineStyleDecl->length() == 0);
+}
+
+String quoteAndEscapeNonPrintables(const String& s)
+{
+    Vector<UChar> result;
+    result.append('"');
+    for (unsigned i = 0; i != s.length(); ++i) {
+        UChar c = s[i];
+        if (c == '\\') {
+            result.append('\\');
+            result.append('\\');
+        } else if (c == '"') {
+            result.append('\\');
+            result.append('"');
+        } else if (c == '\n' || c == noBreakSpace)
+            result.append(' ');
+        else {
+            if (c >= 0x20 && c < 0x7F)
+                result.append(c);
+            else {
+                unsigned u = c;
+                String hex = String::format("\\x{%X}", u);
+                unsigned len = hex.length();
+                for (unsigned i = 0; i < len; ++i)
+                    result.append(hex[i]);
+            }
+        }
+    }
+    result.append('"');
+    return String::adopt(result);
+}
+
+void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior)
+{
+    ts << o.renderName();
+
+    if (behavior & RenderAsTextShowAddresses)
+        ts << " " << static_cast<const void*>(&o);
+
+    if (o.style() && o.style()->zIndex())
+        ts << " zI: " << o.style()->zIndex();
+
+    if (o.node()) {
+        String tagName = getTagName(o.node());
+        if (!tagName.isEmpty()) {
+            ts << " {" << tagName << "}";
+            // flag empty or unstyled AppleStyleSpan because we never
+            // want to leave them in the DOM
+            if (isEmptyOrUnstyledAppleStyleSpan(o.node()))
+                ts << " *empty or unstyled AppleStyleSpan*";
+        }
+    }
+
+    bool adjustForTableCells = o.containingBlock()->isTableCell();
+
+    IntRect r;
+    if (o.isText()) {
+        // 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
+        // many test results.
+        const RenderText& text = *toRenderText(&o);
+        IntRect linesBox = text.linesBoundingBox();
+        r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height());
+        if (adjustForTableCells && !text.firstTextBox())
+            adjustForTableCells = false;
+    } else if (o.isRenderInline()) {
+        // FIXME: Would be better not to just dump 0, 0 as the x and y here.
+        const RenderInline& inlineFlow = *toRenderInline(&o);
+        r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height());
+        adjustForTableCells = false;
+    } else if (o.isTableCell()) {
+        // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect.  We'd like
+        // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are
+        // captured by the results.
+        const RenderTableCell& cell = *toRenderTableCell(&o);
+        r = IntRect(cell.x(), cell.y() + cell.intrinsicPaddingTop(), cell.width(), cell.height() - cell.intrinsicPaddingTop() - cell.intrinsicPaddingBottom());
+    } else if (o.isBox())
+        r = toRenderBox(&o)->frameRect();
+
+    // FIXME: Temporary in order to ensure compatibility with existing layout test results.
+    if (adjustForTableCells)
+        r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingTop());
+
+    ts << " " << r;
+
+    if (!(o.isText() && !o.isBR())) {
+        if (o.isFileUploadControl())
+            ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue());
+
+        if (o.parent() && (o.parent()->style()->color() != o.style()->color()))
+            ts << " [color=" << o.style()->color().name() << "]";
+
+        if (o.parent() && (o.parent()->style()->backgroundColor() != o.style()->backgroundColor()) &&
+            o.style()->backgroundColor().isValid() && o.style()->backgroundColor().rgb())
+            // Do not dump invalid or transparent backgrounds, since that is the default.
+            ts << " [bgcolor=" << o.style()->backgroundColor().name() << "]";
+        
+        if (o.parent() && (o.parent()->style()->textFillColor() != o.style()->textFillColor()) &&
+            o.style()->textFillColor().isValid() && o.style()->textFillColor() != o.style()->color() &&
+            o.style()->textFillColor().rgb())
+            ts << " [textFillColor=" << o.style()->textFillColor().name() << "]";
+
+        if (o.parent() && (o.parent()->style()->textStrokeColor() != o.style()->textStrokeColor()) &&
+            o.style()->textStrokeColor().isValid() && o.style()->textStrokeColor() != o.style()->color() &&
+            o.style()->textStrokeColor().rgb())
+            ts << " [textStrokeColor=" << o.style()->textStrokeColor().name() << "]";
+
+        if (o.parent() && (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth()) &&
+            o.style()->textStrokeWidth() > 0)
+            ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]";
+
+        if (!o.isBoxModelObject())
+            return;
+
+        const RenderBoxModelObject& box = *toRenderBoxModelObject(&o);
+        if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) {
+            ts << " [border:";
+
+            BorderValue prevBorder;
+            if (o.style()->borderTop() != prevBorder) {
+                prevBorder = o.style()->borderTop();
+                if (!box.borderTop())
+                    ts << " none";
+                else {
+                    ts << " (" << box.borderTop() << "px ";
+                    printBorderStyle(ts, o.style()->borderTopStyle());
+                    Color col = o.style()->borderTopColor();
+                    if (!col.isValid())
+                        col = o.style()->color();
+                    ts << col.name() << ")";
+                }
+            }
+
+            if (o.style()->borderRight() != prevBorder) {
+                prevBorder = o.style()->borderRight();
+                if (!box.borderRight())
+                    ts << " none";
+                else {
+                    ts << " (" << box.borderRight() << "px ";
+                    printBorderStyle(ts, o.style()->borderRightStyle());
+                    Color col = o.style()->borderRightColor();
+                    if (!col.isValid())
+                        col = o.style()->color();
+                    ts << col.name() << ")";
+                }
+            }
+
+            if (o.style()->borderBottom() != prevBorder) {
+                prevBorder = box.style()->borderBottom();
+                if (!box.borderBottom())
+                    ts << " none";
+                else {
+                    ts << " (" << box.borderBottom() << "px ";
+                    printBorderStyle(ts, o.style()->borderBottomStyle());
+                    Color col = o.style()->borderBottomColor();
+                    if (!col.isValid())
+                        col = o.style()->color();
+                    ts << col.name() << ")";
+                }
+            }
+
+            if (o.style()->borderLeft() != prevBorder) {
+                prevBorder = o.style()->borderLeft();
+                if (!box.borderLeft())
+                    ts << " none";
+                else {
+                    ts << " (" << box.borderLeft() << "px ";
+                    printBorderStyle(ts, o.style()->borderLeftStyle());
+                    Color col = o.style()->borderLeftColor();
+                    if (!col.isValid())
+                        col = o.style()->color();
+                    ts << col.name() << ")";
+                }
+            }
+
+            ts << "]";
+        }
+    }
+
+    if (o.isTableCell()) {
+        const RenderTableCell& c = *toRenderTableCell(&o);
+        ts << " [r=" << c.row() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
+    }
+
+    if (o.isListMarker()) {
+        String text = toRenderListMarker(&o)->text();
+        if (!text.isEmpty()) {
+            if (text.length() != 1)
+                text = quoteAndEscapeNonPrintables(text);
+            else {
+                switch (text[0]) {
+                    case bullet:
+                        text = "bullet";
+                        break;
+                    case blackSquare:
+                        text = "black square";
+                        break;
+                    case whiteBullet:
+                        text = "white bullet";
+                        break;
+                    default:
+                        text = quoteAndEscapeNonPrintables(text);
+                }
+            }
+            ts << ": " << text;
+        }
+    }
+    
+    if (behavior & RenderAsTextShowIDAndClass) {
+        if (Node* node = o.node()) {
+            if (node->hasID())
+                ts << " id=\"" + static_cast<Element*>(node)->getIdAttribute() + "\"";
+
+            if (node->hasClass()) {
+                StyledElement* styledElement = static_cast<StyledElement*>(node);
+                String classes;
+                for (size_t i = 0; i < styledElement->classNames().size(); ++i) {
+                    if (i > 0)
+                        classes += " ";
+                    classes += styledElement->classNames()[i];
+                }
+                ts << " class=\"" + classes + "\"";
+            }
+        }
+    }
+
+#if PLATFORM(QT)
+    // Print attributes of embedded QWidgets. E.g. when the WebCore::Widget
+    // is invisible the QWidget should be invisible too.
+    if (o.isRenderPart()) {
+        const RenderPart* part = toRenderPart(const_cast<RenderObject*>(&o));
+        if (part->widget() && part->widget()->platformWidget()) {
+            QWidget* wid = part->widget()->platformWidget();
+
+            ts << " [QT: ";
+            ts << "geometry: {" << wid->geometry() << "} ";
+            ts << "isHidden: " << wid->isHidden() << " ";
+            ts << "isSelfVisible: " << part->widget()->isSelfVisible() << " ";
+            ts << "isParentVisible: " << part->widget()->isParentVisible() << " ";
+            ts << "mask: {" << wid->mask().boundingRect() << "} ] ";
+        }
+    }
+#endif
+}
+
+static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run)
+{
+    // FIXME: Table cell adjustment is temporary until results can be updated.
+    int y = run.m_y;
+    if (o.containingBlock()->isTableCell())
+        y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingTop();
+    ts << "text run at (" << run.m_x << "," << y << ") width " << run.m_width;
+    if (run.direction() == RTL || run.m_dirOverride) {
+        ts << (run.direction() == RTL ? " RTL" : " LTR");
+        if (run.m_dirOverride)
+            ts << " override";
+    }
+    ts << ": "
+        << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len()))
+        << "\n";
+}
+
+void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior)
+{
+#if ENABLE(SVG)
+    if (o.isRenderPath()) {
+        write(ts, *toRenderPath(&o), indent);
+        return;
+    }
+    if (o.isSVGGradientStop()) {
+        writeSVGGradientStop(ts, *toRenderSVGGradientStop(&o), indent);
+        return;
+    }
+    if (o.isSVGResourceContainer()) {
+        writeSVGResourceContainer(ts, o, indent);
+        return;
+    }
+    if (o.isSVGContainer()) {
+        writeSVGContainer(ts, o, indent);
+        return;
+    }
+    if (o.isSVGRoot()) {
+        write(ts, *toRenderSVGRoot(&o), indent);
+        return;
+    }
+    if (o.isSVGText()) {
+        writeSVGText(ts, *toRenderBlock(&o), indent);
+        return;
+    }
+    if (o.isSVGInlineText()) {
+        writeSVGInlineText(ts, *toRenderText(&o), indent);
+        return;
+    }
+    if (o.isSVGImage()) {
+        writeSVGImage(ts, *toRenderImage(&o), indent);
+        return;
+    }
+#endif
+
+    writeIndent(ts, indent);
+
+    RenderTreeAsText::writeRenderObject(ts, o, behavior);
+    ts << "\n";
+
+    if (o.isText() && !o.isBR()) {
+        const RenderText& text = *toRenderText(&o);
+        for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
+            writeIndent(ts, indent + 1);
+            writeTextRun(ts, text, *box);
+        }
+    }
+
+    for (RenderObject* child = o.firstChild(); child; child = child->nextSibling()) {
+        if (child->hasLayer())
+            continue;
+        write(ts, *child, indent + 1, behavior);
+    }
+
+    if (o.isWidget()) {
+        Widget* widget = toRenderWidget(&o)->widget();
+        if (widget && widget->isFrameView()) {
+            FrameView* view = static_cast<FrameView*>(widget);
+            RenderView* root = view->frame()->contentRenderer();
+            if (root) {
+                view->layout();
+                RenderLayer* l = root->layer();
+                if (l)
+                    writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), indent + 1, behavior);
+            }
+        }
+    }
+}
+
+enum LayerPaintPhase {
+    LayerPaintPhaseAll = 0,
+    LayerPaintPhaseBackground = -1,
+    LayerPaintPhaseForeground = 1
+};
+
+static void write(TextStream& ts, RenderLayer& l,
+                  const IntRect& layerBounds, const IntRect& backgroundClipRect, const IntRect& clipRect, const IntRect& outlineClipRect,
+                  LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal)
+{
+    writeIndent(ts, indent);
+
+    ts << "layer ";
+    
+    if (behavior & RenderAsTextShowAddresses)
+        ts << static_cast<const void*>(&l) << " ";
+      
+    ts << layerBounds;
+
+    if (!layerBounds.isEmpty()) {
+        if (!backgroundClipRect.contains(layerBounds))
+            ts << " backgroundClip " << backgroundClipRect;
+        if (!clipRect.contains(layerBounds))
+            ts << " clip " << clipRect;
+        if (!outlineClipRect.contains(layerBounds))
+            ts << " outlineClip " << outlineClipRect;
+    }
+
+    if (l.renderer()->hasOverflowClip()) {
+        if (l.scrollXOffset())
+            ts << " scrollX " << l.scrollXOffset();
+        if (l.scrollYOffset())
+            ts << " scrollY " << l.scrollYOffset();
+        if (l.renderBox() && l.renderBox()->clientWidth() != l.scrollWidth())
+            ts << " scrollWidth " << l.scrollWidth();
+        if (l.renderBox() && l.renderBox()->clientHeight() != l.scrollHeight())
+            ts << " scrollHeight " << l.scrollHeight();
+    }
+
+    if (paintPhase == LayerPaintPhaseBackground)
+        ts << " layerType: background only";
+    else if (paintPhase == LayerPaintPhaseForeground)
+        ts << " layerType: foreground only";
+    
+#if USE(ACCELERATED_COMPOSITING)
+    if (behavior & RenderAsTextShowCompositedLayers) {
+        if (l.isComposited())
+            ts << " (composited, bounds " << l.backing()->compositedBounds() << ")";
+    }
+#else
+    UNUSED_PARAM(behavior);
+#endif
+    
+    ts << "\n";
+
+    if (paintPhase != LayerPaintPhaseBackground)
+        write(ts, *l.renderer(), indent + 1, behavior);
+}
+
+static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l,
+                        const IntRect& paintDirtyRect, int indent, RenderAsTextBehavior behavior)
+{
+    // Calculate the clip rects we should use.
+    IntRect layerBounds, damageRect, clipRectToApply, outlineRect;
+    l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, true);
+
+    // Ensure our lists are up-to-date.
+    l->updateZOrderLists();
+    l->updateNormalFlowList();
+
+    bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : l->intersectsDamageRect(layerBounds, damageRect, rootLayer);
+    Vector<RenderLayer*>* negList = l->negZOrderList();
+    bool paintsBackgroundSeparately = negList && negList->size() > 0;
+    if (shouldPaint && paintsBackgroundSeparately)
+        write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, LayerPaintPhaseBackground, indent, behavior);
+
+    if (negList) {
+        int currIndent = indent;
+        if (behavior & RenderAsTextShowLayerNesting) {
+            writeIndent(ts, indent);
+            ts << " negative z-order list(" << negList->size() << ")\n";
+            ++currIndent;
+        }
+        for (unsigned i = 0; i != negList->size(); ++i)
+            writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, currIndent, behavior);
+    }
+
+    if (shouldPaint)
+        write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior);
+
+    if (Vector<RenderLayer*>* normalFlowList = l->normalFlowList()) {
+        int currIndent = indent;
+        if (behavior & RenderAsTextShowLayerNesting) {
+            writeIndent(ts, indent);
+            ts << " normal flow list(" << normalFlowList->size() << ")\n";
+            ++currIndent;
+        }
+        for (unsigned i = 0; i != normalFlowList->size(); ++i)
+            writeLayers(ts, rootLayer, normalFlowList->at(i), paintDirtyRect, currIndent, behavior);
+    }
+
+    if (Vector<RenderLayer*>* posList = l->posZOrderList()) {
+        int currIndent = indent;
+        if (behavior & RenderAsTextShowLayerNesting) {
+            writeIndent(ts, indent);
+            ts << " positive z-order list(" << posList->size() << ")\n";
+            ++currIndent;
+        }
+        for (unsigned i = 0; i != posList->size(); ++i)
+            writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, currIndent, behavior);
+    }
+}
+
+static String nodePosition(Node* node)
+{
+    String result;
+
+    Element* body = node->document()->body();
+    Node* parent;
+    for (Node* n = node; n; n = parent) {
+        parent = n->parentNode();
+        if (!parent)
+            parent = n->shadowParentNode();
+        if (n != node)
+            result += " of ";
+        if (parent) {
+            if (body && n == body) {
+                // We don't care what offset body may be in the document.
+                result += "body";
+                break;
+            }
+            result += "child " + String::number(n->nodeIndex()) + " {" + getTagName(n) + "}";
+        } else
+            result += "document";
+    }
+
+    return result;
+}
+
+static void writeSelection(TextStream& ts, const RenderObject* o)
+{
+    Node* n = o->node();
+    if (!n || !n->isDocumentNode())
+        return;
+
+    Document* doc = static_cast<Document*>(n);
+    Frame* frame = doc->frame();
+    if (!frame)
+        return;
+
+    VisibleSelection selection = frame->selection()->selection();
+    if (selection.isCaret()) {
+        ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node());
+        if (selection.affinity() == UPSTREAM)
+            ts << " (upstream affinity)";
+        ts << "\n";
+    } else if (selection.isRange())
+        ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node()) << "\n"
+           << "selection end:   position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().node()) << "\n";
+}
+
+String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior)
+{
+    PrintContext printContext(frame);
+    if (behavior & RenderAsTextPrintingMode) {
+        if (!frame->contentRenderer())
+            return String();
+        printContext.begin(frame->contentRenderer()->width());
+    }
+
+    frame->document()->updateLayout();
+
+    RenderObject* o = frame->contentRenderer();
+    if (!o)
+        return String();
+
+    TextStream ts;
+    if (o->hasLayer()) {
+        RenderLayer* l = toRenderBox(o)->layer();
+        writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), 0, behavior);
+        writeSelection(ts, o);
+    }
+    return ts.release();
+}
+
+static void writeCounterValuesFromChildren(TextStream& stream, RenderObject* parent, bool& isFirstCounter)
+{
+    for (RenderObject* child = parent->firstChild(); child; child = child->nextSibling()) {
+        if (child->isCounter()) {
+            if (!isFirstCounter)
+                stream << " ";
+            isFirstCounter = false;
+            String str(toRenderText(child)->text());
+            stream << str;
+        }
+    }
+}
+
+String counterValueForElement(Element* element)
+{
+    // Make sure the element is not freed during the layout.
+    RefPtr<Element> elementRef(element);
+    element->document()->updateLayout();
+    TextStream stream;
+    bool isFirstCounter = true;
+    // The counter renderers should be children of anonymous children
+    // (i.e., :before or :after pseudo-elements).
+    if (RenderObject* renderer = element->renderer()) {
+        for (RenderObject* child = renderer->firstChild(); child; child = child->nextSibling()) {
+            if (child->isAnonymous())
+                writeCounterValuesFromChildren(stream, child, isFirstCounter);
+        }
+    }
+    return stream.release();
+}
+
+String markerTextForListItem(Element* element)
+{
+    // Make sure the element is not freed during the layout.
+    RefPtr<Element> elementRef(element);
+    element->document()->updateLayout();
+
+    RenderObject* renderer = element->renderer();
+    if (!renderer || !renderer->isListItem())
+        return String();
+
+    return toRenderListItem(renderer)->markerText();
+}
+
+} // namespace WebCore