/* * 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 "sky/engine/core/rendering/RenderTreeAsText.h" #include "sky/engine/core/css/StylePropertySet.h" #include "sky/engine/core/dom/Document.h" #include "sky/engine/core/frame/FrameView.h" #include "sky/engine/core/frame/LocalFrame.h" #include "sky/engine/core/html/HTMLElement.h" #include "sky/engine/core/rendering/InlineTextBox.h" #include "sky/engine/core/rendering/RenderInline.h" #include "sky/engine/core/rendering/RenderLayer.h" #include "sky/engine/core/rendering/RenderView.h" #include "sky/engine/wtf/HexNumber.h" #include "sky/engine/wtf/Vector.h" #include "sky/engine/wtf/unicode/CharacterNames.h" namespace blink { 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 bool isEmptyOrUnstyledAppleStyleSpan(const Node* node) { return false; } String quoteAndEscapeNonPrintables(const String& s) { StringBuilder 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 { result.append('\\'); result.append('x'); result.append('{'); appendUnsignedAsHex(c, result); result.append('}'); } } } result.append('"'); return result.toString(); } 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 = o.node()->nodeName(); 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*"; } } LayoutRect 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()); } 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()); } else if (o.isBox()) r = toRenderBox(&o)->frameRect(); ts << " " << r; if (!o.isText()) { if (o.parent()) { Color color = o.resolveColor(CSSPropertyColor); if (o.parent()->resolveColor(CSSPropertyColor) != color) ts << " [color=" << color.nameForRenderTreeAsText() << "]"; // Do not dump invalid or transparent backgrounds, since that is the default. Color backgroundColor = o.resolveColor(CSSPropertyBackgroundColor); if (o.parent()->resolveColor(CSSPropertyBackgroundColor) != backgroundColor && backgroundColor.rgb()) ts << " [bgcolor=" << backgroundColor.nameForRenderTreeAsText() << "]"; Color textFillColor = o.resolveColor(CSSPropertyWebkitTextFillColor); if (o.parent()->resolveColor(CSSPropertyWebkitTextFillColor) != textFillColor && textFillColor != color && textFillColor.rgb()) ts << " [textFillColor=" << textFillColor.nameForRenderTreeAsText() << "]"; Color textStrokeColor = o.resolveColor(CSSPropertyWebkitTextStrokeColor); if (o.parent()->resolveColor(CSSPropertyWebkitTextStrokeColor) != textStrokeColor && textStrokeColor != color && textStrokeColor.rgb()) ts << " [textStrokeColor=" << textStrokeColor.nameForRenderTreeAsText() << "]"; if (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 = o.style()->borderTop(); if (!box.borderTop()) ts << " none"; else { ts << " (" << box.borderTop() << "px "; printBorderStyle(ts, o.style()->borderTopStyle()); Color col = o.resolveColor(CSSPropertyBorderTopColor); ts << col.nameForRenderTreeAsText() << ")"; } 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.resolveColor(CSSPropertyBorderRightColor); ts << col.nameForRenderTreeAsText() << ")"; } } 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.resolveColor(CSSPropertyBorderBottomColor); ts << col.nameForRenderTreeAsText() << ")"; } } 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.resolveColor(CSSPropertyBorderLeftColor); ts << col.nameForRenderTreeAsText() << ")"; } } ts << "]"; } } if (behavior & RenderAsTextShowIDAndClass) { Node* node = o.node(); if (node && node->isElementNode()) { Element& element = toElement(*node); if (element.hasID()) ts << " id=\"" + element.getIdAttribute() + "\""; if (element.hasClass()) { ts << " class=\""; for (size_t i = 0; i < element.classNames().size(); ++i) { if (i > 0) ts << " "; ts << element.classNames()[i]; } ts << "\""; } } } if (behavior & RenderAsTextShowLayoutState) { bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout(); if (needsLayout) ts << " (needs layout:"; bool havePrevious = false; if (o.selfNeedsLayout()) { ts << " self"; havePrevious = true; } if (o.needsPositionedMovementLayout()) { if (havePrevious) ts << ","; havePrevious = true; ts << " positioned movement"; } if (o.normalChildNeedsLayout()) { if (havePrevious) ts << ","; havePrevious = true; ts << " child"; } if (o.posChildNeedsLayout()) { if (havePrevious) ts << ","; ts << " positioned child"; } if (needsLayout) ts << ")"; } } static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) { // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder // to detect any changes caused by the conversion to floating point. :( int x = run.x(); int y = run.y(); int logicalWidth = ceilf(run.left() + run.logicalWidth()) - x; ts << "text run at (" << x << "," << y << ") width " << logicalWidth; if (!run.isLeftToRightDirection() || run.dirOverride()) { ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR"); if (run.dirOverride()) ts << " override"; } ts << ": " << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len())); if (run.hasHyphen()) ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style()->hyphenString()); ts << "\n"; } void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior) { writeIndent(ts, indent); RenderTreeAsText::writeRenderObject(ts, o, behavior); ts << "\n"; if (o.isText()) { 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.slowFirstChild(); child; child = child->nextSibling()) { if (child->hasLayer()) continue; write(ts, *child, indent + 1, behavior); } } static void write(TextStream& ts, RenderLayer& l, const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal) { IntRect adjustedLayoutBounds = pixelSnappedIntRect(layerBounds); IntRect adjustedBackgroundClipRect = pixelSnappedIntRect(backgroundClipRect); writeIndent(ts, indent); ts << "layer "; if (behavior & RenderAsTextShowAddresses) ts << static_cast(&l) << " "; ts << adjustedLayoutBounds; if (!adjustedLayoutBounds.isEmpty()) { if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds)) ts << " backgroundClip " << adjustedBackgroundClipRect; } ts << "\n"; write(ts, *l.renderer(), indent + 1, behavior); } void RenderTreeAsText::writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* layer, const LayoutRect& paintRect, int indent, RenderAsTextBehavior behavior) { // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh. LayoutRect paintDirtyRect(paintRect); if (rootLayer == layer) { paintDirtyRect.setWidth(max(paintDirtyRect.width(), rootLayer->renderer()->layoutOverflowRect().maxX())); paintDirtyRect.setHeight(max(paintDirtyRect.height(), rootLayer->renderer()->layoutOverflowRect().maxY())); } // Calculate the clip rects we should use. LayoutRect layerBounds; ClipRect damageRect; layer->clipper().calculateRects(ClipRectsContext(rootLayer, UncachedClipRects), paintDirtyRect, layerBounds, damageRect); // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh. if (rootLayer == layer) layerBounds.setSize(layer->size().expandedTo(pixelSnappedIntSize(layer->renderer()->maxLayoutOverflow(), LayoutPoint(0, 0)))); // Ensure our lists are up-to-date. layer->stackingNode()->updateLayerListsIfNeeded(); bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : layer->intersectsDamageRect(layerBounds, damageRect.rect(), rootLayer); if (shouldPaint) write(ts, *layer, layerBounds, damageRect.rect(), indent, behavior); if (Vector* normalFlowList = layer->stackingNode()->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)->layer(), paintDirtyRect, currIndent, behavior); } if (Vector* posList = layer->stackingNode()->zOrderList()) { 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)->layer(), paintDirtyRect, currIndent, behavior); } } String nodePositionAsStringForTesting(Node* node) { StringBuilder result; Node* parent; for (Node* n = node; n; n = parent) { parent = n->parentNode(); if (n != node) result.appendLiteral(" of "); if (parent) { result.appendLiteral("child "); result.appendNumber(n->nodeIndex()); result.appendLiteral(" {"); result.append(n->nodeName()); result.append('}'); } else result.appendLiteral("document"); } return result.toString(); } static String externalRepresentation(RenderBox* renderer, RenderAsTextBehavior behavior) { TextStream ts; if (!renderer->hasLayer()) return ts.release(); RenderLayer* layer = renderer->layer(); RenderTreeAsText::writeLayers(ts, layer, layer, layer->rect(), 0, behavior); return ts.release(); } String externalRepresentation(LocalFrame* frame, RenderAsTextBehavior behavior) { if (!(behavior & RenderAsTextDontUpdateLayout)) frame->document()->updateLayout(); RenderObject* renderer = frame->contentRenderer(); if (!renderer || !renderer->isBox()) return String(); return externalRepresentation(toRenderBox(renderer), behavior); } String externalRepresentation(Element* element, RenderAsTextBehavior behavior) { // Doesn't support printing mode. if (!(behavior & RenderAsTextDontUpdateLayout)) element->document().updateLayout(); RenderObject* renderer = element->renderer(); if (!renderer || !renderer->isBox()) return String(); return externalRepresentation(toRenderBox(renderer), behavior | RenderAsTextShowAllLayers); } } // namespace blink