blob: 41f1793680afae15de26919c5456ab38d7244626 [file] [log] [blame]
/*
* 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<const void*>(&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<const void*>(&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<LayoutUnit>(paintDirtyRect.width(), rootLayer->renderer()->layoutOverflowRect().maxX()));
paintDirtyRect.setHeight(max<LayoutUnit>(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<RenderLayerStackingNode*>* 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<RenderLayerStackingNode*>* 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