diff -r 000000000000 -r 4f2f89ce4247 WebCore/rendering/RenderTreeAsText.cpp --- /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 +#include + +#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 +#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(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 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(&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(node)->getIdAttribute() + "\""; + + if (node->hasClass()) { + StyledElement* styledElement = static_cast(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(&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(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(&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* 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* 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* 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(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 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 elementRef(element); + element->document()->updateLayout(); + + RenderObject* renderer = element->renderer(); + if (!renderer || !renderer->isListItem()) + return String(); + + return toRenderListItem(renderer)->markerText(); +} + +} // namespace WebCore