blob: fa86b988f4cf43a6f956574323cbeede92209353 [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 "core/layout/LayoutTreeAsText.h"
#include "core/HTMLNames.h"
#include "core/css/StylePropertySet.h"
#include "core/dom/Document.h"
#include "core/dom/PseudoElement.h"
#include "core/editing/FrameSelection.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLElement.h"
#include "core/layout/LayoutBlockFlow.h"
#include "core/layout/LayoutDetailsMarker.h"
#include "core/layout/LayoutFileUploadControl.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutListItem.h"
#include "core/layout/LayoutListMarker.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/LayoutTableCell.h"
#include "core/layout/LayoutView.h"
#include "core/layout/compositing/CompositedLayerMapping.h"
#include "core/layout/line/InlineTextBox.h"
#include "core/layout/svg/LayoutSVGGradientStop.h"
#include "core/layout/svg/LayoutSVGImage.h"
#include "core/layout/svg/LayoutSVGInlineText.h"
#include "core/layout/svg/LayoutSVGRoot.h"
#include "core/layout/svg/LayoutSVGShape.h"
#include "core/layout/svg/LayoutSVGText.h"
#include "core/layout/svg/SVGLayoutTreeAsText.h"
#include "core/page/PrintContext.h"
#include "core/paint/PaintLayer.h"
#include "platform/LayoutUnit.h"
#include "wtf/HexNumber.h"
#include "wtf/Vector.h"
#include "wtf/text/CharacterNames.h"
namespace blink {
using namespace HTMLNames;
static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle)
{
switch (borderStyle) {
case BorderStyleNone:
ts << "none";
break;
case BorderStyleHidden:
ts << "hidden";
break;
case BorderStyleInset:
ts << "inset";
break;
case BorderStyleGroove:
ts << "groove";
break;
case BorderStyleRidge:
ts << "ridge";
break;
case BorderStyleOutset:
ts << "outset";
break;
case BorderStyleDotted:
ts << "dotted";
break;
case BorderStyleDashed:
ts << "dashed";
break;
case BorderStyleSolid:
ts << "solid";
break;
case BorderStyleDouble:
ts << "double";
break;
}
ts << " ";
}
static String getTagName(Node* n)
{
if (n->isDocumentNode())
return "";
if (n->getNodeType() == Node::kCommentNode)
return "COMMENT";
return n->nodeName();
}
static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
{
if (!isHTMLSpanElement(node))
return false;
const HTMLElement& elem = toHTMLElement(*node);
if (elem.getAttribute(classAttr) != "Apple-style-span")
return false;
if (!elem.hasChildren())
return true;
const StylePropertySet* inlineStyleDecl = elem.inlineStyle();
return (!inlineStyleDecl || inlineStyleDecl->isEmpty());
}
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 == noBreakSpaceCharacter) {
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();
}
TextStream& operator<<(TextStream& ts, const Color& c)
{
return ts << c.nameForLayoutTreeAsText();
}
void LayoutTreeAsText::writeLayoutObject(TextStream& ts, const LayoutObject& o, LayoutAsTextBehavior behavior)
{
ts << o.decoratedName();
if (behavior & LayoutAsTextShowAddresses)
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*";
}
}
LayoutBlock* cb = o.containingBlock();
bool adjustForTableCells = cb ? cb->isTableCell() : false;
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 LayoutText& text = toLayoutText(o);
IntRect linesBox = enclosingIntRect(text.linesBoundingBox());
r = LayoutRect(IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height()));
if (adjustForTableCells && !text.hasTextBoxes())
adjustForTableCells = false;
} else if (o.isLayoutInline()) {
// FIXME: Would be better not to just dump 0, 0 as the x and y here.
const LayoutInline& inlineFlow = toLayoutInline(o);
IntRect linesBox = enclosingIntRect(inlineFlow.linesBoundingBox());
r = LayoutRect(IntRect(0, 0, linesBox.width(), linesBox.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 LayoutTableCell& cell = toLayoutTableCell(o);
r = LayoutRect(cell.location().x(), cell.location().y() + cell.intrinsicPaddingBefore(), cell.size().width(), cell.size().height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter());
} else if (o.isBox()) {
r = toLayoutBox(&o)->frameRect();
}
// FIXME: Temporary in order to ensure compatibility with existing layout test results.
if (adjustForTableCells)
r.move(0, -toLayoutTableCell(o.containingBlock())->intrinsicPaddingBefore());
if (o.isLayoutView()) {
r.setWidth(LayoutUnit(toLayoutView(o).viewWidth(IncludeScrollbars)));
r.setHeight(LayoutUnit(toLayoutView(o).viewHeight(IncludeScrollbars)));
}
ts << " " << r;
if (!(o.isText() && !o.isBR())) {
if (o.isFileUploadControl())
ts << " " << quoteAndEscapeNonPrintables(toLayoutFileUploadControl(&o)->fileTextValue());
if (o.parent()) {
Color color = o.resolveColor(CSSPropertyColor);
if (o.parent()->resolveColor(CSSPropertyColor) != color)
ts << " [color=" << color << "]";
// 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 << "]";
Color textFillColor = o.resolveColor(CSSPropertyWebkitTextFillColor);
if (o.parent()->resolveColor(CSSPropertyWebkitTextFillColor) != textFillColor
&& textFillColor != color && textFillColor.rgb())
ts << " [textFillColor=" << textFillColor << "]";
Color textStrokeColor = o.resolveColor(CSSPropertyWebkitTextStrokeColor);
if (o.parent()->resolveColor(CSSPropertyWebkitTextStrokeColor) != textStrokeColor
&& textStrokeColor != color && textStrokeColor.rgb())
ts << " [textStrokeColor=" << textStrokeColor << "]";
if (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth() && o.style()->textStrokeWidth() > 0)
ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]";
}
if (!o.isBoxModelObject())
return;
const LayoutBoxModelObject& box = toLayoutBoxModelObject(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());
ts << o.resolveColor(CSSPropertyBorderTopColor) << ")";
}
if (o.style()->borderRight() != prevBorder) {
prevBorder = o.style()->borderRight();
if (!box.borderRight()) {
ts << " none";
} else {
ts << " (" << box.borderRight() << "px ";
printBorderStyle(ts, o.style()->borderRightStyle());
ts << o.resolveColor(CSSPropertyBorderRightColor) << ")";
}
}
if (o.style()->borderBottom() != prevBorder) {
prevBorder = box.style()->borderBottom();
if (!box.borderBottom()) {
ts << " none";
} else {
ts << " (" << box.borderBottom() << "px ";
printBorderStyle(ts, o.style()->borderBottomStyle());
ts << o.resolveColor(CSSPropertyBorderBottomColor) << ")";
}
}
if (o.style()->borderLeft() != prevBorder) {
prevBorder = o.style()->borderLeft();
if (!box.borderLeft()) {
ts << " none";
} else {
ts << " (" << box.borderLeft() << "px ";
printBorderStyle(ts, o.style()->borderLeftStyle());
ts << o.resolveColor(CSSPropertyBorderLeftColor) << ")";
}
}
ts << "]";
}
}
if (o.isTableCell()) {
const LayoutTableCell& c = toLayoutTableCell(o);
ts << " [r=" << c.rowIndex() << " c=" << c.absoluteColumnIndex() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
}
if (o.isDetailsMarker()) {
ts << ": ";
switch (toLayoutDetailsMarker(&o)->getOrientation()) {
case LayoutDetailsMarker::Left:
ts << "left";
break;
case LayoutDetailsMarker::Right:
ts << "right";
break;
case LayoutDetailsMarker::Up:
ts << "up";
break;
case LayoutDetailsMarker::Down:
ts << "down";
break;
}
}
if (o.isListMarker()) {
String text = toLayoutListMarker(o).text();
if (!text.isEmpty()) {
if (text.length() != 1) {
text = quoteAndEscapeNonPrintables(text);
} else {
switch (text[0]) {
case bulletCharacter:
text = "bullet";
break;
case blackSquareCharacter:
text = "black square";
break;
case whiteBulletCharacter:
text = "white bullet";
break;
default:
text = quoteAndEscapeNonPrintables(text);
}
}
ts << ": " << text;
}
}
if (behavior & LayoutAsTextShowIDAndClass) {
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 & LayoutAsTextShowLayoutState) {
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 writeInlineBox(TextStream& ts, const InlineBox& box, int indent)
{
writeIndent(ts, indent);
ts << "+ ";
ts << box.boxName() << " {" << box.getLineLayoutItem().debugName() << "}"
<< " pos=(" << box.x() << "," << box.y() << ")"
<< " size=(" << box.width() << "," << box.height() << ")"
<< " baseline=" << box.baselinePosition(AlphabeticBaseline)
<< "/" << box.baselinePosition(IdeographicBaseline);
}
static void writeInlineTextBox(TextStream& ts, const InlineTextBox& textBox, int indent)
{
writeInlineBox(ts, textBox, indent);
String value = textBox.text();
value.replace('\\', "\\\\");
value.replace('\n', "\\n");
value.replace('"', "\\\"");
ts << " range=(" << textBox.start() << "," << (textBox.start() + textBox.len()) << ")"
<< " \"" << value << "\"";
}
static void writeInlineFlowBox(TextStream& ts, const InlineFlowBox& rootBox, int indent)
{
writeInlineBox(ts, rootBox, indent);
ts << "\n";
for (const InlineBox* box = rootBox.firstChild(); box; box = box->nextOnLine()) {
if (box->isInlineFlowBox()) {
writeInlineFlowBox(ts, static_cast<const InlineFlowBox&>(*box), indent + 1);
continue;
}
if (box->isInlineTextBox())
writeInlineTextBox(ts, static_cast<const InlineTextBox&>(*box), indent + 1);
else
writeInlineBox(ts, *box, indent + 1);
ts << "\n";
}
}
void LayoutTreeAsText::writeLineBoxTree(TextStream& ts, const LayoutBlockFlow& o, int indent)
{
for (const InlineFlowBox* rootBox = o.firstLineBox(); rootBox; rootBox = rootBox->nextLineBox()) {
writeInlineFlowBox(ts, *rootBox, indent);
}
}
static void writeTextRun(TextStream& ts, const LayoutText& 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 = (run.left() + run.logicalWidth()).ceil() - x;
// FIXME: Table cell adjustment is temporary until results can be updated.
if (o.containingBlock()->isTableCell())
y -= toLayoutTableCell(o.containingBlock())->intrinsicPaddingBefore();
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 LayoutObject& o, int indent, LayoutAsTextBehavior behavior)
{
if (o.isSVGShape()) {
write(ts, toLayoutSVGShape(o), indent);
return;
}
if (o.isSVGGradientStop()) {
writeSVGGradientStop(ts, toLayoutSVGGradientStop(o), indent);
return;
}
if (o.isSVGResourceContainer()) {
writeSVGResourceContainer(ts, o, indent);
return;
}
if (o.isSVGContainer()) {
writeSVGContainer(ts, o, indent);
return;
}
if (o.isSVGRoot()) {
write(ts, toLayoutSVGRoot(o), indent);
return;
}
if (o.isSVGText()) {
writeSVGText(ts, toLayoutSVGText(o), indent);
return;
}
if (o.isSVGInlineText()) {
writeSVGInlineText(ts, toLayoutSVGInlineText(o), indent);
return;
}
if (o.isSVGImage()) {
writeSVGImage(ts, toLayoutSVGImage(o), indent);
return;
}
writeIndent(ts, indent);
LayoutTreeAsText::writeLayoutObject(ts, o, behavior);
ts << "\n";
if ((behavior & LayoutAsTextShowLineTrees) && o.isLayoutBlockFlow()) {
LayoutTreeAsText::writeLineBoxTree(ts, toLayoutBlockFlow(o), indent + 1);
}
if (o.isText() && !o.isBR()) {
const LayoutText& text = toLayoutText(o);
for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
writeIndent(ts, indent + 1);
writeTextRun(ts, text, *box);
}
}
for (LayoutObject* child = o.slowFirstChild(); child; child = child->nextSibling()) {
if (child->hasLayer())
continue;
write(ts, *child, indent + 1, behavior);
}
if (o.isLayoutPart()) {
Widget* widget = toLayoutPart(o).widget();
if (widget && widget->isFrameView()) {
FrameView* view = toFrameView(widget);
LayoutViewItem rootItem = view->layoutViewItem();
if (!rootItem.isNull()) {
rootItem.updateStyleAndLayout();
PaintLayer* layer = rootItem.layer();
if (layer)
LayoutTreeAsText::writeLayers(ts, layer, layer, layer->rect(), indent + 1, behavior);
}
}
}
}
enum LayerPaintPhase {
LayerPaintPhaseAll = 0,
LayerPaintPhaseBackground = -1,
LayerPaintPhaseForeground = 1
};
static void write(TextStream& ts, PaintLayer& layer,
const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, const LayoutRect& clipRect,
LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, LayoutAsTextBehavior behavior = LayoutAsTextBehaviorNormal,
const PaintLayer* markedLayer = nullptr)
{
IntRect adjustedLayoutBounds = pixelSnappedIntRect(layerBounds);
IntRect adjustedLayoutBoundsWithScrollbars = adjustedLayoutBounds;
IntRect adjustedBackgroundClipRect = pixelSnappedIntRect(backgroundClipRect);
IntRect adjustedClipRect = pixelSnappedIntRect(clipRect);
Settings* settings = layer.layoutObject()->document().settings();
bool reportFrameScrollInfo = layer.layoutObject()->isLayoutView() && settings && !settings->rootLayerScrolls();
if (reportFrameScrollInfo) {
LayoutView* layoutView = toLayoutView(layer.layoutObject());
adjustedLayoutBoundsWithScrollbars.setWidth(layoutView->viewWidth(IncludeScrollbars));
adjustedLayoutBoundsWithScrollbars.setHeight(layoutView->viewHeight(IncludeScrollbars));
}
if (markedLayer)
ts << (markedLayer == &layer ? "*" : " ");
writeIndent(ts, indent);
if (layer.layoutObject()->style()->visibility() == EVisibility::Hidden)
ts << "hidden ";
ts << "layer ";
if (behavior & LayoutAsTextShowAddresses)
ts << static_cast<const void*>(&layer) << " ";
ts << adjustedLayoutBoundsWithScrollbars;
if (!adjustedLayoutBounds.isEmpty()) {
if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds))
ts << " backgroundClip " << adjustedBackgroundClipRect;
if (!adjustedClipRect.contains(adjustedLayoutBoundsWithScrollbars))
ts << " clip " << adjustedClipRect;
}
if (layer.isTransparent())
ts << " transparent";
if (layer.layoutObject()->hasOverflowClip() || reportFrameScrollInfo) {
ScrollableArea* scrollableArea;
if (reportFrameScrollInfo)
scrollableArea = toLayoutView(layer.layoutObject())->frameView();
else
scrollableArea = layer.getScrollableArea();
DoublePoint adjustedScrollOffset = scrollableArea->scrollPositionDouble() + toDoubleSize(scrollableArea->scrollOrigin());
if (adjustedScrollOffset.x())
ts << " scrollX " << adjustedScrollOffset.x();
if (adjustedScrollOffset.y())
ts << " scrollY " << adjustedScrollOffset.y();
if (layer.layoutBox() && layer.layoutBox()->pixelSnappedClientWidth() != layer.layoutBox()->pixelSnappedScrollWidth())
ts << " scrollWidth " << layer.layoutBox()->pixelSnappedScrollWidth();
if (layer.layoutBox() && layer.layoutBox()->pixelSnappedClientHeight() != layer.layoutBox()->pixelSnappedScrollHeight())
ts << " scrollHeight " << layer.layoutBox()->pixelSnappedScrollHeight();
}
if (paintPhase == LayerPaintPhaseBackground)
ts << " layerType: background only";
else if (paintPhase == LayerPaintPhaseForeground)
ts << " layerType: foreground only";
if (layer.layoutObject()->style()->hasBlendMode())
ts << " blendMode: " << compositeOperatorName(CompositeSourceOver, layer.layoutObject()->style()->blendMode());
if (behavior & LayoutAsTextShowCompositedLayers) {
if (layer.hasCompositedLayerMapping()) {
ts << " (composited, bounds="
<< layer.compositedLayerMapping()->compositedBounds()
<< ", drawsContent="
<< layer.compositedLayerMapping()->mainGraphicsLayer()->drawsContent()
<< (layer.shouldIsolateCompositedDescendants() ? ", isolatesCompositedBlending" : "")
<< ")";
}
}
ts << "\n";
if (paintPhase != LayerPaintPhaseBackground)
write(ts, *layer.layoutObject(), indent + 1, behavior);
}
static Vector<PaintLayerStackingNode*> normalFlowListFor(PaintLayerStackingNode* node)
{
PaintLayerStackingNodeIterator it(*node, NormalFlowChildren);
Vector<PaintLayerStackingNode*> vector;
while (PaintLayerStackingNode* normalFlowChild = it.next())
vector.append(normalFlowChild);
return vector;
}
void LayoutTreeAsText::writeLayers(TextStream& ts, const PaintLayer* rootLayer, PaintLayer* layer,
const LayoutRect& paintRect, int indent, LayoutAsTextBehavior behavior, const PaintLayer* markedLayer)
{
// Calculate the clip rects we should use.
LayoutRect layerBounds;
ClipRect damageRect, clipRectToApply;
layer->clipper().calculateRects(ClipRectsContext(rootLayer, UncachedClipRects), paintRect, layerBounds, damageRect, clipRectToApply);
// Ensure our lists are up to date.
layer->stackingNode()->updateLayerListsIfNeeded();
LayoutPoint offsetFromRoot;
layer->convertToLayerCoords(rootLayer, offsetFromRoot);
bool shouldPaint = (behavior & LayoutAsTextShowAllLayers) ? true : layer->intersectsDamageRect(layerBounds, damageRect.rect(), offsetFromRoot);
if (layer->layoutObject()->isLayoutPart() && toLayoutPart(layer->layoutObject())->isThrottledFrameView())
shouldPaint = false;
Vector<PaintLayerStackingNode*>* negList = layer->stackingNode()->negZOrderList();
bool paintsBackgroundSeparately = negList && negList->size() > 0;
if (shouldPaint && paintsBackgroundSeparately)
write(ts, *layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), LayerPaintPhaseBackground, indent, behavior, markedLayer);
if (negList) {
int currIndent = indent;
if (behavior & LayoutAsTextShowLayerNesting) {
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)->layer(), paintRect, currIndent, behavior, markedLayer);
}
if (shouldPaint)
write(ts, *layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior, markedLayer);
Vector<PaintLayerStackingNode*> normalFlowList = normalFlowListFor(layer->stackingNode());
if (!normalFlowList.isEmpty()) {
int currIndent = indent;
if (behavior & LayoutAsTextShowLayerNesting) {
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(), paintRect, currIndent, behavior, markedLayer);
}
if (Vector<PaintLayerStackingNode*>* posList = layer->stackingNode()->posZOrderList()) {
int currIndent = indent;
if (behavior & LayoutAsTextShowLayerNesting) {
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(), paintRect, currIndent, behavior, markedLayer);
}
}
String nodePositionAsStringForTesting(Node* node)
{
StringBuilder result;
Element* body = node->document().body();
Node* parent;
for (Node* n = node; n; n = parent) {
parent = n->parentOrShadowHostNode();
if (n != node)
result.append(" of ");
if (parent) {
if (body && n == body) {
// We don't care what offset body may be in the document.
result.append("body");
break;
}
if (n->isShadowRoot()) {
result.append('{');
result.append(getTagName(n));
result.append('}');
} else {
result.append("child ");
result.appendNumber(n->nodeIndex());
result.append(" {");
result.append(getTagName(n));
result.append('}');
}
} else {
result.append("document");
}
}
return result.toString();
}
static void writeSelection(TextStream& ts, const LayoutObject* o)
{
Node* n = o->node();
if (!n || !n->isDocumentNode())
return;
Document* doc = toDocument(n);
LocalFrame* frame = doc->frame();
if (!frame)
return;
VisibleSelection selection = frame->selection().selection();
if (selection.isCaret()) {
ts << "caret: position " << selection.start().computeEditingOffset() << " of " << nodePositionAsStringForTesting(selection.start().anchorNode());
if (selection.affinity() == TextAffinity::Upstream)
ts << " (upstream affinity)";
ts << "\n";
} else if (selection.isRange()) {
ts << "selection start: position " << selection.start().computeEditingOffset() << " of " << nodePositionAsStringForTesting(selection.start().anchorNode()) << "\n"
<< "selection end: position " << selection.end().computeEditingOffset() << " of " << nodePositionAsStringForTesting(selection.end().anchorNode()) << "\n";
}
}
static String externalRepresentation(LayoutBox* layoutObject, LayoutAsTextBehavior behavior, const PaintLayer* markedLayer = nullptr)
{
TextStream ts;
if (!layoutObject->hasLayer())
return ts.release();
PaintLayer* layer = layoutObject->layer();
LayoutTreeAsText::writeLayers(ts, layer, layer, layer->rect(), 0, behavior, markedLayer);
writeSelection(ts, layoutObject);
return ts.release();
}
String externalRepresentation(LocalFrame* frame, LayoutAsTextBehavior behavior, const PaintLayer* markedLayer)
{
if (!(behavior & LayoutAsTextDontUpdateLayout))
frame->document()->updateStyleAndLayout();
LayoutObject* layoutObject = frame->contentLayoutObject();
if (!layoutObject || !layoutObject->isBox())
return String();
PrintContext printContext(frame);
if (behavior & LayoutAsTextPrintingMode) {
FloatSize size(toLayoutBox(layoutObject)->size());
printContext.begin(size.width(), size.height());
}
return externalRepresentation(toLayoutBox(layoutObject), behavior, markedLayer);
}
String externalRepresentation(Element* element, LayoutAsTextBehavior behavior)
{
// Doesn't support printing mode.
ASSERT(!(behavior & LayoutAsTextPrintingMode));
if (!(behavior & LayoutAsTextDontUpdateLayout))
element->document().updateStyleAndLayout();
LayoutObject* layoutObject = element->layoutObject();
if (!layoutObject || !layoutObject->isBox())
return String();
return externalRepresentation(toLayoutBox(layoutObject), behavior | LayoutAsTextShowAllLayers);
}
static void writeCounterValuesFromChildren(TextStream& stream, LayoutObject* parent, bool& isFirstCounter)
{
for (LayoutObject* child = parent->slowFirstChild(); child; child = child->nextSibling()) {
if (child->isCounter()) {
if (!isFirstCounter)
stream << " ";
isFirstCounter = false;
String str(toLayoutText(child)->text());
stream << str;
}
}
}
String counterValueForElement(Element* element)
{
element->document().updateStyleAndLayout();
TextStream stream;
bool isFirstCounter = true;
// The counter layoutObjects should be children of :before or :after pseudo-elements.
if (LayoutObject* before = element->pseudoElementLayoutObject(PseudoIdBefore))
writeCounterValuesFromChildren(stream, before, isFirstCounter);
if (LayoutObject* after = element->pseudoElementLayoutObject(PseudoIdAfter))
writeCounterValuesFromChildren(stream, after, isFirstCounter);
return stream.release();
}
String markerTextForListItem(Element* element)
{
element->document().updateStyleAndLayout();
LayoutObject* layoutObject = element->layoutObject();
if (!layoutObject || !layoutObject->isListItem())
return String();
return toLayoutListItem(layoutObject)->markerText();
}
} // namespace blink