diff options
Diffstat (limited to 'WebCore/rendering/RenderTreeAsText.cpp')
-rw-r--r-- | WebCore/rendering/RenderTreeAsText.cpp | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/WebCore/rendering/RenderTreeAsText.cpp b/WebCore/rendering/RenderTreeAsText.cpp new file mode 100644 index 0000000..15bc6c0 --- /dev/null +++ b/WebCore/rendering/RenderTreeAsText.cpp @@ -0,0 +1,512 @@ +/* + * 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 "CharacterNames.h" +#include "Document.h" +#include "Frame.h" +#include "FrameView.h" +#include "HTMLElement.h" +#include "HTMLNames.h" +#include "InlineTextBox.h" +#include "RenderBR.h" +#include "RenderListMarker.h" +#include "RenderTableCell.h" +#include "RenderView.h" +#include "RenderWidget.h" +#include "SelectionController.h" +#include "TextStream.h" +#include <wtf/Vector.h> + +#if ENABLE(SVG) +#include "RenderSVGRoot.h" +#include "RenderSVGContainer.h" +#include "RenderSVGInlineText.h" +#include "RenderSVGText.h" +#include "SVGRenderTreeAsText.h" +#endif + +namespace WebCore { + +using namespace HTMLNames; + +static void writeLayers(TextStream&, const RenderLayer* rootLayer, RenderLayer*, const IntRect& paintDirtyRect, int indent = 0); + +#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 + +static void writeIndent(TextStream& ts, int indent) +{ + for (int i = 0; i != indent; ++i) + ts << " "; +} + +static void printBorderStyle(TextStream& ts, const RenderObject& o, 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); +} + +static TextStream &operator<<(TextStream& ts, const RenderObject& o) +{ + ts << o.renderName(); + + if (o.style() && o.style()->zIndex()) + ts << " zI: " << o.style()->zIndex(); + + if (o.element()) { + String tagName = getTagName(o.element()); + if (!tagName.isEmpty()) { + ts << " {" << tagName << "}"; + // flag empty or unstyled AppleStyleSpan because we never + // want to leave them in the DOM + if (isEmptyOrUnstyledAppleStyleSpan(o.element())) + ts << " *empty or unstyled AppleStyleSpan*"; + } + } + + IntRect r(o.xPos(), o.yPos(), o.width(), o.height()); + ts << " " << r; + + if (!(o.isText() && !o.isBR())) { + 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.borderTop() || o.borderRight() || o.borderBottom() || o.borderLeft()) { + ts << " [border:"; + + BorderValue prevBorder; + if (o.style()->borderTop() != prevBorder) { + prevBorder = o.style()->borderTop(); + if (!o.borderTop()) + ts << " none"; + else { + ts << " (" << o.borderTop() << "px "; + printBorderStyle(ts, o, 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 (!o.borderRight()) + ts << " none"; + else { + ts << " (" << o.borderRight() << "px "; + printBorderStyle(ts, o, o.style()->borderRightStyle()); + Color col = o.style()->borderRightColor(); + if (!col.isValid()) + col = o.style()->color(); + ts << col.name() << ")"; + } + } + + if (o.style()->borderBottom() != prevBorder) { + prevBorder = o.style()->borderBottom(); + if (!o.borderBottom()) + ts << " none"; + else { + ts << " (" << o.borderBottom() << "px "; + printBorderStyle(ts, o, 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 (!o.borderLeft()) + ts << " none"; + else { + ts << " (" << o.borderLeft() << "px "; + printBorderStyle(ts, o, 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 = static_cast<const RenderTableCell&>(o); + ts << " [r=" << c.row() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]"; + } + + if (o.isListMarker()) { + String text = static_cast<const RenderListMarker&>(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; + } + } + + return ts; +} + +static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) +{ + ts << "text run at (" << run.m_x << "," << run.m_y << ") width " << run.m_width; + if (run.m_reversed || run.m_dirOverride) { + ts << (run.m_reversed ? " RTL" : " LTR"); + if (run.m_dirOverride) + ts << " override"; + } + ts << ": " + << quoteAndEscapeNonPrintables(String(o.text()).substring(run.m_start, run.m_len)) + << "\n"; +} + +void write(TextStream& ts, const RenderObject& o, int indent) +{ +#if ENABLE(SVG) + if (o.isRenderPath()) { + write(ts, static_cast<const RenderPath&>(o), indent); + return; + } + if (o.isSVGContainer()) { + write(ts, static_cast<const RenderSVGContainer&>(o), indent); + return; + } + if (o.isSVGRoot()) { + write(ts, static_cast<const RenderSVGRoot&>(o), indent); + return; + } + if (o.isSVGText()) { + if (!o.isText()) + write(ts, static_cast<const RenderSVGText&>(o), indent); + else + write(ts, static_cast<const RenderSVGInlineText&>(o), indent); + return; + } +#endif + + writeIndent(ts, indent); + + ts << o << "\n"; + + if (o.isText() && !o.isBR()) { + const RenderText& text = static_cast<const RenderText&>(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); + } + + if (o.isWidget()) { + Widget* widget = static_cast<const RenderWidget&>(o).widget(); + if (widget && widget->isFrameView()) { + FrameView* view = static_cast<FrameView*>(widget); + RenderObject* root = view->frame()->renderer(); + if (root) { + view->layout(); + RenderLayer* l = root->layer(); + if (l) + writeLayers(ts, l, l, IntRect(l->xPos(), l->yPos(), l->width(), l->height()), indent + 1); + } + } + } +} + +static void write(TextStream& ts, RenderLayer& l, + const IntRect& layerBounds, const IntRect& backgroundClipRect, const IntRect& clipRect, const IntRect& outlineClipRect, + int layerType = 0, int indent = 0) +{ + writeIndent(ts, indent); + + ts << "layer " << 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.renderer()->clientWidth() != l.scrollWidth()) + ts << " scrollWidth " << l.scrollWidth(); + if (l.renderer()->clientHeight() != l.scrollHeight()) + ts << " scrollHeight " << l.scrollHeight(); + } + + if (layerType == -1) + ts << " layerType: background only"; + else if (layerType == 1) + ts << " layerType: foreground only"; + + ts << "\n"; + + if (layerType != -1) + write(ts, *l.renderer(), indent + 1); +} + +static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l, + const IntRect& paintDirtyRect, int indent) +{ + // Calculate the clip rects we should use. + IntRect layerBounds, damageRect, clipRectToApply, outlineRect; + l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect); + + // Ensure our lists are up-to-date. + l->updateZOrderLists(); + l->updateOverflowList(); + + bool shouldPaint = l->intersectsDamageRect(layerBounds, damageRect, rootLayer); + Vector<RenderLayer*>* negList = l->negZOrderList(); + if (shouldPaint && negList && negList->size() > 0) + write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, -1, indent); + + if (negList) { + for (unsigned i = 0; i != negList->size(); ++i) + writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, indent); + } + + if (shouldPaint) + write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, negList && negList->size() > 0, indent); + + Vector<RenderLayer*>* overflowList = l->overflowList(); + if (overflowList) { + for (unsigned i = 0; i != overflowList->size(); ++i) + writeLayers(ts, rootLayer, overflowList->at(i), paintDirtyRect, indent); + } + + Vector<RenderLayer*>* posList = l->posZOrderList(); + if (posList) { + for (unsigned i = 0; i != posList->size(); ++i) + writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, indent); + } +} + +static String nodePosition(Node* node) +{ + String result; + + Node* parent; + for (Node* n = node; n; n = parent) { + parent = n->parentNode(); + if (!parent) + parent = n->shadowParentNode(); + if (n != node) + result += " of "; + if (parent) + result += "child " + String::number(n->nodeIndex()) + " {" + getTagName(n) + "}"; + else + result += "document"; + } + + return result; +} + +static void writeSelection(TextStream& ts, const RenderObject* o) +{ + Node* n = o->element(); + if (!n || !n->isDocumentNode()) + return; + + Document* doc = static_cast<Document*>(n); + Frame* frame = doc->frame(); + if (!frame) + return; + + Selection selection = frame->selectionController()->selection(); + if (selection.isCaret()) { + ts << "caret: position " << selection.start().offset() << " of " << nodePosition(selection.start().node()); + if (selection.affinity() == UPSTREAM) + ts << " (upstream affinity)"; + ts << "\n"; + } else if (selection.isRange()) + ts << "selection start: position " << selection.start().offset() << " of " << nodePosition(selection.start().node()) << "\n" + << "selection end: position " << selection.end().offset() << " of " << nodePosition(selection.end().node()) << "\n"; +} + +String externalRepresentation(RenderObject* o) +{ + if (!o) + return String(); + + TextStream ts; +#if ENABLE(SVG) + writeRenderResources(ts, o->document()); +#endif + if (o->view()->frameView()) + o->view()->frameView()->layout(); + RenderLayer* l = o->layer(); + if (l) { + writeLayers(ts, l, l, IntRect(l->xPos(), l->yPos(), l->width(), l->height())); + writeSelection(ts, o); + } + return ts.release(); +} + +} // namespace WebCore |