blob: 2470bcc3c789e479346c4234e6a54880bda98e96 [file] [log] [blame]
/*
* Copyright (C) 2004-2017 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 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 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 "ClipRect.h"
#include "ColorSerialization.h"
#include "ContainerNodeInlines.h"
#include "Document.h"
#include "ElementInlines.h"
#include "FrameSelection.h"
#include "GraphicsLayer.h"
#include "HTMLElement.h"
#include "HTMLNames.h"
#include "HTMLSpanElement.h"
#include "InlineIteratorBoxInlines.h"
#include "InlineIteratorTextBox.h"
#include "LegacyRenderSVGContainer.h"
#include "LegacyRenderSVGImage.h"
#include "LegacyRenderSVGResourceContainer.h"
#include "LegacyRenderSVGRoot.h"
#include "LegacyRenderSVGShape.h"
#include "LocalFrame.h"
#include "LocalFrameView.h"
#include "Logging.h"
#include "NodeInlines.h"
#include "PrintContext.h"
#include "PseudoElement.h"
#include "RemoteFrame.h"
#include "RemoteFrameView.h"
#include "RenderBlockFlow.h"
#include "RenderBoxModelObjectInlines.h"
#include "RenderCounter.h"
#include "RenderElementInlines.h"
#include "RenderFileUploadControl.h"
#include "RenderFragmentContainer.h"
#include "RenderInline.h"
#include "RenderIterator.h"
#include "RenderLayerBacking.h"
#include "RenderLayerInlines.h"
#include "RenderLayerScrollableArea.h"
#include "RenderLineBreak.h"
#include "RenderListItem.h"
#include "RenderListMarker.h"
#include "RenderObjectInlines.h"
#include "RenderQuote.h"
#include "RenderSVGContainer.h"
#include "RenderSVGGradientStop.h"
#include "RenderSVGInlineText.h"
#include "RenderSVGRoot.h"
#include "RenderSVGShapeInlines.h"
#include "RenderSVGText.h"
#include "RenderTableCell.h"
#include "RenderView.h"
#include "RenderWidget.h"
#include "SVGRenderTreeAsText.h"
#include "ScriptDisallowedScope.h"
#include "ShadowRoot.h"
#include "StylePropertiesInlines.h"
#include "StylePrimitiveKeyword+Logging.h"
#include "StylePrimitiveNumericTypes+Logging.h"
#include <wtf/HexNumber.h>
#include <wtf/Vector.h>
#include <wtf/text/TextStream.h>
#include <wtf/unicode/CharacterNames.h>
#if PLATFORM(MAC)
#include "ScrollbarThemeMac.h"
#endif
namespace WebCore {
using namespace HTMLNames;
static void writeLayers(TextStream&, const RenderLayer& rootLayer, RenderLayer&, const LayoutRect& paintDirtyRect, OptionSet<RenderAsTextFlag>);
static void printBorderStyle(TextStream& ts, const BorderStyle borderStyle)
{
switch (borderStyle) {
case BorderStyle::None:
ts << "none"_s;
break;
case BorderStyle::Hidden:
ts << "hidden"_s;
break;
case BorderStyle::Inset:
ts << "inset"_s;
break;
case BorderStyle::Groove:
ts << "groove"_s;
break;
case BorderStyle::Ridge:
ts << "ridge"_s;
break;
case BorderStyle::Outset:
ts << "outset"_s;
break;
case BorderStyle::Dotted:
ts << "dotted"_s;
break;
case BorderStyle::Dashed:
ts << "dashed"_s;
break;
case BorderStyle::Solid:
ts << "solid"_s;
break;
case BorderStyle::Double:
ts << "double"_s;
break;
}
ts << ' ';
}
static String getTagName(Node* n)
{
if (n->isDocumentNode())
return ""_s;
if (n->nodeType() == Node::COMMENT_NODE)
return "COMMENT"_s;
return n->nodeName();
}
static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
{
auto* element = dynamicDowncast<HTMLSpanElement>(node);
if (!element)
return false;
if (element->getAttribute(classAttr) != "Apple-style-span"_s)
return false;
if (!node->hasChildNodes())
return true;
SUPPRESS_UNCOUNTED_LOCAL const StyleProperties* inlineStyleDecl = element->inlineStyle();
return (!inlineStyleDecl || inlineStyleDecl->isEmpty());
}
String quoteAndEscapeNonPrintables(StringView s)
{
StringBuilder result;
result.append('"');
for (unsigned i = 0; i != s.length(); ++i) {
char16_t c = s[i];
if (c == '\\') {
result.append("\\\\"_s);
} else if (c == '"') {
result.append("\\\""_s);
} else if (c == '\n' || c == noBreakSpace)
result.append(' ');
else {
if (c >= 0x20 && c < 0x7F)
result.append(c);
else
result.append("\\x{"_s, hex(c), '}');
}
}
result.append('"');
return result.toString();
}
inline bool shouldEnableSubpixelPrecisionForTextDump(const Document& document)
{
// If LBSE is activated and the document contains outermost <svg> elements, generate the text
// representation with subpixel precision. It would be awkward to only see the SVG part of a
// compound document with subpixel precision in the render tree dumps, and not the surrounding content.
return document.settings().layerBasedSVGEngineEnabled() && document.mayHaveRenderedSVGRootElements();
}
void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, OptionSet<RenderAsTextFlag> behavior)
{
ts << o.renderName().characters();
if (behavior.contains(RenderAsTextFlag::ShowAddresses))
ts << ' ' << &o;
if (auto value = o.style().usedZIndex().tryValue(); value && value->value) // FIXME: This should log even when value->value is zero.
ts << " zI: "_s << value->value;
if (o.node()) {
String tagName = getTagName(o.node());
// FIXME: Temporary hack to make tests pass by simulating the old generated content output.
if (o.isPseudoElement() || (o.parent() && o.parent()->isPseudoElement()))
tagName = emptyAtom();
if (!tagName.isEmpty()) {
ts << " {"_s << 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*"_s;
}
}
bool enableSubpixelPrecisionForTextDump = shouldEnableSubpixelPrecisionForTextDump(o.document());
LayoutRect r;
if (auto* text = dynamicDowncast<RenderText>(o))
r = text->linesBoundingBox();
else if (auto* br = dynamicDowncast<RenderLineBreak>(o); br && br->isBR())
r = br->linesBoundingBox();
else if (auto* inlineFlow = dynamicDowncast<RenderInline>(o))
r = inlineFlow->linesBoundingBox();
else if (auto* cell = dynamicDowncast<RenderTableCell>(o)) {
// 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.
r = LayoutRect(cell->x(), cell->y() + cell->intrinsicPaddingBefore(), cell->width(), cell->height() - cell->intrinsicPaddingBefore() - cell->intrinsicPaddingAfter());
} else if (auto* box = dynamicDowncast<RenderBox>(o))
r = box->frameRect();
else if (auto* svgModelObject = dynamicDowncast<RenderSVGModelObject>(o)) {
r = svgModelObject->frameRectEquivalent();
ASSERT(r.location() == svgModelObject->currentSVGLayoutLocation());
}
// FIXME: Convert layout test results to report sub-pixel values, in the meantime using enclosingIntRect
// for consistency with old results.
if (enableSubpixelPrecisionForTextDump)
ts << ' ' << r;
else
ts << ' ' << enclosingIntRect(r);
if (auto* svgModelObject = dynamicDowncast<RenderSVGModelObject>(o)) {
writeSVGPaintingFeatures(ts, *svgModelObject, behavior);
if (auto* svgShape = dynamicDowncast<RenderSVGShape>(*svgModelObject))
writeSVGGraphicsElement(ts, svgShape->graphicsElement());
writeDebugInfo(ts, o, behavior);
return;
}
if (CheckedPtr renderElement = dynamicDowncast<RenderElement>(o)) {
if (auto* control = dynamicDowncast<RenderFileUploadControl>(*renderElement))
ts << ' ' << quoteAndEscapeNonPrintables(control->fileTextValue());
if (renderElement->parent()) {
auto color = renderElement->style().visitedDependentColor();
if (!equalIgnoringSemanticColor(renderElement->parent()->style().visitedDependentColor(), color))
ts << " [color="_s << serializationForRenderTreeAsText(color) << ']';
// Do not dump invalid or transparent backgrounds, since that is the default.
auto backgroundColor = renderElement->style().visitedDependentBackgroundColor();
if (!equalIgnoringSemanticColor(renderElement->parent()->style().visitedDependentBackgroundColor(), backgroundColor)
&& backgroundColor != Color::transparentBlack)
ts << " [bgcolor="_s << serializationForRenderTreeAsText(backgroundColor) << ']';
auto textFillColor = renderElement->style().visitedDependentTextFillColor();
if (!equalIgnoringSemanticColor(renderElement->parent()->style().visitedDependentTextFillColor(), textFillColor)
&& textFillColor != color && textFillColor != Color::transparentBlack)
ts << " [textFillColor="_s << serializationForRenderTreeAsText(textFillColor) << ']';
auto textStrokeColor = renderElement->style().visitedDependentTextStrokeColor();
if (!equalIgnoringSemanticColor(renderElement->parent()->style().visitedDependentTextStrokeColor(), textStrokeColor)
&& textStrokeColor != color && textStrokeColor != Color::transparentBlack)
ts << " [textStrokeColor="_s << serializationForRenderTreeAsText(textStrokeColor) << ']';
if (renderElement->parent()->style().textStrokeWidth() != renderElement->style().textStrokeWidth() && renderElement->style().textStrokeWidth().isPositive())
ts << " [textStrokeWidth="_s << Style::evaluate<float>(renderElement->style().textStrokeWidth(), renderElement->style().usedZoomForLength()) << ']';
}
auto* box = dynamicDowncast<RenderBoxModelObject>(o);
if (!box || is<RenderLineBreak>(*box))
return;
LayoutUnit borderTop = box->borderTop();
LayoutUnit borderRight = box->borderRight();
LayoutUnit borderBottom = box->borderBottom();
LayoutUnit borderLeft = box->borderLeft();
bool overridden = renderElement->style().borderImageWidth().overridesBorderWidths();
if (box->isFieldset()) {
const auto& block = downcast<RenderBlock>(*box);
switch (renderElement->writingMode().blockDirection()) {
case FlowDirection::TopToBottom:
borderTop -= block.intrinsicBorderForFieldset();
break;
case FlowDirection::BottomToTop:
borderBottom -= block.intrinsicBorderForFieldset();
break;
case FlowDirection::LeftToRight:
borderLeft -= block.intrinsicBorderForFieldset();
break;
case FlowDirection::RightToLeft:
borderRight -= block.intrinsicBorderForFieldset();
}
}
if (borderTop || borderRight || borderBottom || borderLeft) {
ts << " [border:"_s;
auto printBorder = [&] (const LayoutUnit& width, const BorderStyle& style, const Color& resolvedColor) {
if (!width)
ts << " none"_s;
else {
ts << " ("_s << width << "px "_s;
printBorderStyle(ts, style);
ts << serializationForRenderTreeAsText(resolvedColor) << ')';
}
};
BorderValue prevBorder = renderElement->style().borderTop();
printBorder(borderTop, renderElement->style().borderTopStyle(), renderElement->style().borderTopColorResolvingCurrentColor());
if (renderElement->style().borderRight() != prevBorder || (overridden && borderRight != borderTop)) {
prevBorder = renderElement->style().borderRight();
printBorder(borderRight, renderElement->style().borderRightStyle(), renderElement->style().borderRightColorResolvingCurrentColor());
}
if (renderElement->style().borderBottom() != prevBorder || (overridden && borderBottom != borderRight)) {
prevBorder = renderElement->style().borderBottom();
printBorder(borderBottom, renderElement->style().borderBottomStyle(), renderElement->style().borderBottomColorResolvingCurrentColor());
}
if (renderElement->style().borderLeft() != prevBorder || (overridden && borderLeft != borderBottom)) {
prevBorder = renderElement->style().borderLeft();
printBorder(borderLeft, renderElement->style().borderLeftStyle(), renderElement->style().borderLeftColorResolvingCurrentColor());
}
ts << ']';
}
#if ENABLE(MATHML)
// We want to show any layout padding, both CSS padding and intrinsic padding, so we can't just check o.style().hasPadding().
if (o.isRenderMathMLBlock() && (box->paddingTop() || box->paddingRight() || box->paddingBottom() || box->paddingLeft())) {
ts << " ["_s;
LayoutUnit cssTop = box->computedCSSPaddingTop();
LayoutUnit cssRight = box->computedCSSPaddingRight();
LayoutUnit cssBottom = box->computedCSSPaddingBottom();
LayoutUnit cssLeft = box->computedCSSPaddingLeft();
if (box->paddingTop() != cssTop || box->paddingRight() != cssRight || box->paddingBottom() != cssBottom || box->paddingLeft() != cssLeft) {
ts << "intrinsic "_s;
if (cssTop || cssRight || cssBottom || cssLeft)
ts << "+ CSS "_s;
}
ts << "padding: "_s << roundToInt(box->paddingTop()) << ' ' << roundToInt(box->paddingRight()) << ' ' << roundToInt(box->paddingBottom()) << ' ' << roundToInt(box->paddingLeft()) << ']';
}
#endif
}
if (auto* cell = dynamicDowncast<RenderTableCell>(o))
ts << " [r="_s << cell->rowIndex() << " c="_s << cell->col() << " rs="_s << cell->rowSpan() << " cs="_s << cell->colSpan() << ']';
if (auto* listMarker = dynamicDowncast<RenderListMarker>(o)) {
auto text = listMarker->textWithoutSuffix();
if (!text.isEmpty()) {
if (text.length() != 1)
text = quoteAndEscapeNonPrintables(text);
else {
switch (text[0]) {
case bullet:
text = "bullet"_s;
break;
case blackSquare:
text = "black square"_s;
break;
case whiteBullet:
text = "white bullet"_s;
break;
default:
text = quoteAndEscapeNonPrintables(text);
}
}
ts << ": "_s << text;
}
}
writeDebugInfo(ts, o, behavior);
}
void writeDebugInfo(TextStream& ts, const RenderObject& object, OptionSet<RenderAsTextFlag> behavior)
{
if (behavior.contains(RenderAsTextFlag::ShowIDAndClass)) {
if (RefPtr element = dynamicDowncast<Element>(object.node())) {
if (element->hasID())
ts << " id=\"" << element->getIdAttribute() << '"';
if (element->hasClass()) {
ts << " class=\""_s;
for (size_t i = 0; i < element->classNames().size(); ++i) {
if (i > 0)
ts << ' ';
ts << element->classNames()[i];
}
ts << '"';
}
}
}
if (behavior.contains(RenderAsTextFlag::ShowLayoutState)) {
bool needsLayout = object.selfNeedsLayout() || object.needsOutOfFlowMovementLayout() || object.outOfFlowChildNeedsLayout() || object.normalChildNeedsLayout();
if (needsLayout)
ts << " (needs layout:"_s;
bool havePrevious = false;
if (object.selfNeedsLayout()) {
ts << " self"_s;
havePrevious = true;
}
if (object.needsOutOfFlowMovementLayout()) {
if (havePrevious)
ts << ',';
havePrevious = true;
ts << " positioned movement"_s;
}
if (object.normalChildNeedsLayout()) {
if (havePrevious)
ts << ',';
havePrevious = true;
ts << " child"_s;
}
if (object.outOfFlowChildNeedsLayout()) {
if (havePrevious)
ts << ',';
ts << " positioned child"_s;
}
if (needsLayout)
ts << ')';
}
if (behavior.contains(RenderAsTextFlag::ShowOverflow)) {
if (auto* box = dynamicDowncast<RenderBox>(object)) {
if (box->hasRenderOverflow()) {
LayoutRect layoutOverflow = box->layoutOverflowRect();
ts << " (layout overflow "_s << layoutOverflow.x().toInt() << ',' << layoutOverflow.y().toInt() << ' ' << layoutOverflow.width().toInt() << 'x' << layoutOverflow.height().toInt() << ')';
if (box->hasVisualOverflow()) {
LayoutRect visualOverflow = box->visualOverflowRect();
ts << " (visual overflow "_s << visualOverflow.x().toInt() << ',' << visualOverflow.y().toInt() << ' ' << visualOverflow.width().toInt() << 'x' << visualOverflow.height().toInt() << ')';
}
}
}
if (auto* renderSVGModelObject = dynamicDowncast<RenderSVGModelObject>(object)) {
if (renderSVGModelObject->hasVisualOverflow()) {
auto visualOverflow = renderSVGModelObject->visualOverflowRectEquivalent();
ts << " (visual overflow "_s << visualOverflow.x() << ',' << visualOverflow.y() << ' ' << visualOverflow.width() << 'x' << visualOverflow.height() << ')';
}
}
}
}
static inline void writeTextRuns(TextStream& ts, auto& textRenderer)
{
auto writeTextRun = [&] (auto& textRun) {
ts << indent;
auto rect = textRun.visualRectIgnoringBlockDirection();
int x = rect.x();
int y = rect.y();
// FIXME: Use non-logical width. webkit.org/b/206809.
int logicalWidth = ceilf(rect.x() + (textRun.isHorizontal() ? rect.width() : rect.height())) - x;
// FIXME: Table cell adjustment is temporary until results can be updated.
if (auto* tableCell = dynamicDowncast<RenderTableCell>(*textRenderer.containingBlock()))
y -= floorToInt(tableCell->intrinsicPaddingBefore());
ts << "text run at ("_s << x << ',' << y << ") width "_s << logicalWidth;
if (!textRun.isLeftToRightDirection())
ts << " RTL"_s;
ts << ": "_s
<< quoteAndEscapeNonPrintables(textRun.originalText());
if (textRun.hasHyphen())
ts << " + hyphen string "_s << quoteAndEscapeNonPrintables(textRenderer.style().hyphenString().string());
ts << '\n';
};
for (auto& run : InlineIterator::textBoxesFor(textRenderer))
writeTextRun(run);
}
static inline void writeSVGRenderer(TextStream& ts, const RenderObject& renderer, OptionSet<RenderAsTextFlag> behavior)
{
if (auto* svgShape = dynamicDowncast<LegacyRenderSVGShape>(renderer)) {
write(ts, *svgShape, behavior);
return;
}
if (auto* svgGradientStop = dynamicDowncast<RenderSVGGradientStop>(renderer)) {
writeSVGGradientStop(ts, *svgGradientStop, behavior);
return;
}
if (auto* svgResourceContainer = dynamicDowncast<LegacyRenderSVGResourceContainer>(renderer)) {
writeSVGResourceContainer(ts, *svgResourceContainer, behavior);
return;
}
if (auto* svgContainer = dynamicDowncast<LegacyRenderSVGContainer>(renderer)) {
writeSVGContainer(ts, *svgContainer, behavior);
return;
}
if (auto* svgRoot = dynamicDowncast<LegacyRenderSVGRoot>(renderer)) {
write(ts, *svgRoot, behavior);
return;
}
if (auto* text = dynamicDowncast<RenderSVGText>(renderer)) {
writeSVGText(ts, *text, behavior);
return;
}
if (auto* inlineText = dynamicDowncast<RenderSVGInlineText>(renderer)) {
writeSVGInlineText(ts, *inlineText, behavior);
return;
}
if (auto* svgImage = dynamicDowncast<LegacyRenderSVGImage>(renderer)) {
writeSVGImage(ts, *svgImage, behavior);
return;
}
}
void write(TextStream& ts, const RenderObject& renderer, OptionSet<RenderAsTextFlag> behavior)
{
if (is<LegacyRenderSVGShape>(renderer) || is<RenderSVGGradientStop>(renderer) || is<LegacyRenderSVGResourceContainer>(renderer)
|| is<LegacyRenderSVGContainer>(renderer) || is<LegacyRenderSVGRoot>(renderer) || is<RenderSVGText>(renderer)
|| is<RenderSVGInlineText>(renderer) || is<LegacyRenderSVGImage>(renderer)) {
writeSVGRenderer(ts, renderer, behavior);
return;
}
ts << indent;
RenderTreeAsText::writeRenderObject(ts, renderer, behavior);
ts << '\n';
TextStream::IndentScope indentScope(ts);
if (auto* textRenderer = dynamicDowncast<RenderText>(renderer)) {
writeTextRuns(ts, *textRenderer);
return;
}
for (auto& child : childrenOfType<RenderObject>(downcast<RenderElement>(renderer))) {
if (child.hasLayer())
continue;
write(ts, child, behavior);
}
if (auto* renderWidget = dynamicDowncast<RenderWidget>(renderer); renderWidget && renderWidget->widget() && is<FrameView>(renderWidget->widget()))
dynamicDowncast<FrameView>(renderWidget->widget())->writeRenderTreeAsText(ts, behavior);
if (is<RenderSVGModelObject>(renderer) || is<RenderSVGRoot>(renderer))
writeResources(ts, renderer, behavior);
}
enum LayerPaintPhase {
LayerPaintPhaseAll = 0,
LayerPaintPhaseBackground = -1,
LayerPaintPhaseForeground = 1
};
template<typename DumpRectType>
inline void writeLayerUsingGeometryType(TextStream& ts, const RenderLayer& layer, const DumpRectType& layerBounds, const DumpRectType& backgroundClipRect, const DumpRectType& clipRect,
LayerPaintPhase paintPhase, OptionSet<RenderAsTextFlag> behavior)
{
ts << indent << "layer "_s;
if (behavior.contains(RenderAsTextFlag::ShowAddresses)) {
ts << &layer << ' ';
if (auto* scrollableArea = layer.scrollableArea())
ts << "scrollableArea "_s << scrollableArea << ' ';
}
ts << layerBounds;
if (!layerBounds.isEmpty()) {
if (!backgroundClipRect.contains(layerBounds))
ts << " backgroundClip "_s << backgroundClipRect;
if (!clipRect.contains(layerBounds))
ts << " clip "_s << clipRect;
}
if (layer.renderer().hasNonVisibleOverflow()) {
if (auto* scrollableArea = layer.scrollableArea()) {
if (scrollableArea->scrollOffset().x())
ts << " scrollX "_s << scrollableArea->scrollOffset().x();
if (scrollableArea->scrollOffset().y())
ts << " scrollY "_s << scrollableArea->scrollOffset().y();
if (layer.renderBox() && roundToInt(layer.renderBox()->clientWidth()) != scrollableArea->scrollWidth())
ts << " scrollWidth "_s << scrollableArea->scrollWidth();
if (layer.renderBox() && roundToInt(layer.renderBox()->clientHeight()) != scrollableArea->scrollHeight())
ts << " scrollHeight "_s << scrollableArea->scrollHeight();
}
#if PLATFORM(MAC)
ScrollbarTheme& scrollbarTheme = ScrollbarTheme::theme();
if (!scrollbarTheme.isMockTheme() && layer.scrollableArea() && layer.scrollableArea()->hasVerticalScrollbar()) {
ScrollbarThemeMac& macTheme = *downcast<ScrollbarThemeMac>(&scrollbarTheme);
if (macTheme.isLayoutDirectionRTL(*layer.scrollableArea()->verticalScrollbar()))
ts << " scrollbarHasRTLLayoutDirection"_s;
}
#endif
}
if (paintPhase == LayerPaintPhaseBackground)
ts << " layerType: background only"_s;
else if (paintPhase == LayerPaintPhaseForeground)
ts << " layerType: foreground only"_s;
if (behavior.contains(RenderAsTextFlag::ShowCompositedLayers)) {
if (layer.isComposited()) {
ts << " (composited "_s << layer.compositor().reasonsForCompositing(layer)
<< ", bounds=" << layer.backing()->compositedBounds()
<< ", drawsContent=" << layer.backing()->graphicsLayer()->drawsContent()
<< ", paints into ancestor=" << layer.backing()->paintsIntoCompositedAncestor() << ")";
} else if (layer.paintsIntoProvidedBacking())
ts << " (shared backing of "_s << layer.backingProviderLayer() << ')';
}
if (layer.isolatesBlending())
ts << " isolatesBlending"_s;
if (layer.hasBlendMode())
ts << " blendMode: "_s << compositeOperatorName(CompositeOperator::SourceOver, layer.blendMode());
ts << '\n';
}
static void writeLayer(TextStream& ts, const RenderLayer& layer, const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, const LayoutRect& clipRect,
LayerPaintPhase paintPhase = LayerPaintPhaseAll, OptionSet<RenderAsTextFlag> behavior = { })
{
if (shouldEnableSubpixelPrecisionForTextDump(layer.renderer().document())) {
writeLayerUsingGeometryType<LayoutRect>(ts, layer, layerBounds, backgroundClipRect, clipRect, paintPhase, behavior);
return;
}
writeLayerUsingGeometryType<IntRect>(ts, layer, snappedIntRect(layerBounds), snappedIntRect(backgroundClipRect), snappedIntRect(clipRect), paintPhase, behavior);
}
static void writeLayerRenderers(TextStream& ts, const RenderLayer& layer, LayerPaintPhase paintPhase, OptionSet<RenderAsTextFlag> behavior)
{
if (paintPhase != LayerPaintPhaseBackground) {
TextStream::IndentScope indentScope(ts);
write(ts, layer.renderer(), behavior);
}
}
static LayoutSize maxLayoutOverflow(const RenderBox* box)
{
LayoutRect overflowRect = box->layoutOverflowRect();
return LayoutSize(overflowRect.maxX(), overflowRect.maxY());
}
static void writeLayers(TextStream& ts, const RenderLayer& rootLayer, RenderLayer& layer, const LayoutRect& paintRect, OptionSet<RenderAsTextFlag> behavior)
{
// FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh.
LayoutRect paintDirtyRect(paintRect);
if (&rootLayer == &layer) {
paintDirtyRect.setWidth(std::max<LayoutUnit>(paintDirtyRect.width(), rootLayer.renderBox()->layoutOverflowRect().maxX()));
paintDirtyRect.setHeight(std::max<LayoutUnit>(paintDirtyRect.height(), rootLayer.renderBox()->layoutOverflowRect().maxY()));
layer.setSize(layer.size().expandedTo(snappedIntSize(maxLayoutOverflow(layer.renderBox()), LayoutPoint(0, 0))));
layer.setNeedsPositionUpdate();
}
// Calculate the clip rects we should use.
LayoutSize offsetFromRoot = layer.offsetFromAncestor(&rootLayer);
RenderLayer::ClipRectsContext clipRectsContext(&rootLayer, PaintingClipRects, RenderLayer::clipRectTemporaryOptions);
auto rects = layer.calculateRects(clipRectsContext, offsetFromRoot, paintDirtyRect);
// Ensure our lists are up-to-date.
layer.updateLayerListsIfNeeded();
layer.updateDescendantDependentFlags();
bool shouldPaint = (behavior.contains(RenderAsTextFlag::ShowAllLayers)) ? true : layer.intersectsDamageRect(rects.layerBounds(), rects.dirtyBackgroundRect().rect(), &rootLayer, layer.offsetFromAncestor(&rootLayer));
auto negativeZOrderLayers = layer.negativeZOrderLayers();
bool paintsBackgroundSeparately = negativeZOrderLayers.size() > 0;
if (shouldPaint && paintsBackgroundSeparately) {
writeLayer(ts, layer, rects.layerBounds(), rects.dirtyBackgroundRect().rect(), rects.dirtyForegroundRect().rect(), LayerPaintPhaseBackground, behavior);
writeLayerRenderers(ts, layer, LayerPaintPhaseBackground, behavior);
}
if (negativeZOrderLayers.size()) {
if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) {
ts << indent << " negative z-order list ("_s << negativeZOrderLayers.size() << ")\n"_s;
ts.increaseIndent();
}
for (CheckedPtr currLayer : negativeZOrderLayers)
writeLayers(ts, rootLayer, *currLayer, paintDirtyRect, behavior);
if (behavior.contains(RenderAsTextFlag::ShowLayerNesting))
ts.decreaseIndent();
}
if (shouldPaint) {
writeLayer(ts, layer, rects.layerBounds(), rects.dirtyBackgroundRect().rect(), rects.dirtyForegroundRect().rect(), paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, behavior);
if (behavior.contains(RenderAsTextFlag::ShowLayerFragments)) {
LayerFragments layerFragments;
layer.collectFragments(layerFragments, &rootLayer, paintDirtyRect, RenderLayer::PaginationInclusionMode::ExcludeCompositedPaginatedLayers, PaintingClipRects, RenderLayer::clipRectTemporaryOptions, offsetFromRoot);
if (layerFragments.size() > 1) {
TextStream::IndentScope indentScope(ts, 2);
for (unsigned i = 0; i < layerFragments.size(); ++i) {
const auto& fragment = layerFragments[i];
ts << indent << " fragment "_s << i << ": bounds in layer "_s << fragment.layerBounds() << " fragment bounds "_s << fragment.boundingBox() << '\n';
}
}
}
writeLayerRenderers(ts, layer, paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, behavior);
}
auto normalFlowLayers = layer.normalFlowLayers();
if (normalFlowLayers.size()) {
if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) {
ts << indent << " normal flow list ("_s << normalFlowLayers.size() << ")\n"_s;
ts.increaseIndent();
}
for (CheckedPtr currLayer : normalFlowLayers)
writeLayers(ts, rootLayer, *currLayer, paintDirtyRect, behavior);
if (behavior.contains(RenderAsTextFlag::ShowLayerNesting))
ts.decreaseIndent();
}
auto positiveZOrderLayers = layer.positiveZOrderLayers();
if (positiveZOrderLayers.size()) {
size_t layerCount = positiveZOrderLayers.size();
if (layerCount) {
if (behavior.contains(RenderAsTextFlag::ShowLayerNesting)) {
ts << indent << " positive z-order list ("_s << layerCount << ")\n"_s;
ts.increaseIndent();
}
for (CheckedPtr currLayer : positiveZOrderLayers)
writeLayers(ts, rootLayer, *currLayer, paintDirtyRect, behavior);
if (behavior.contains(RenderAsTextFlag::ShowLayerNesting))
ts.decreaseIndent();
}
}
}
static String nodePosition(Node* node)
{
StringBuilder result;
RefPtr body = node->document().bodyOrFrameset();
RefPtr<Node> parent;
for (RefPtr n = node; n; n = parent) {
parent = n->parentOrShadowHostNode();
if (n != node)
result.append(" of "_s);
if (parent) {
if (body && n == body) {
// We don't care what offset body may be in the document.
result.append("body"_s);
break;
}
if (n->isShadowRoot())
result.append('{', getTagName(n), '}');
else
result.append("child "_s, n->computeNodeIndex(), " {"_s, getTagName(n), '}');
} else
result.append("document"_s);
}
return result.toString();
}
static void writeSelection(TextStream& ts, const RenderBox& renderer)
{
if (!renderer.isRenderView())
return;
RefPtr frame = renderer.document().frame();
if (!frame)
return;
VisibleSelection selection = frame->selection().selection();
if (selection.isCaret()) {
ts << "caret: position "_s << selection.start().deprecatedEditingOffset() << " of "_s << nodePosition(selection.start().deprecatedNode());
if (selection.affinity() == Affinity::Upstream)
ts << " (upstream affinity)"_s;
ts << '\n';
} else if (selection.isRange())
ts << "selection start: position "_s << selection.start().deprecatedEditingOffset() << " of "_s << nodePosition(selection.start().deprecatedNode()) << '\n'
<< "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().deprecatedNode()) << "\n";
}
static TextStream createTextStream(const Document& document)
{
auto formattingFlags = [&document]() -> OptionSet<TextStream::Formatting> {
if (shouldEnableSubpixelPrecisionForTextDump(document))
return { TextStream::Formatting::SVGStyleRect };
return { TextStream::Formatting::SVGStyleRect, TextStream::Formatting::LayoutUnitsAsIntegers };
};
return { TextStream::LineMode::MultipleLine, formattingFlags() };
}
TextStream createTextStream(const RenderView& view)
{
return createTextStream(view.document());
}
static String externalRepresentation(RenderBox& renderer, OptionSet<RenderAsTextFlag> behavior)
{
auto ts = createTextStream(renderer.document());
if (!renderer.hasLayer())
return ts.release();
LOG(Layout, "externalRepresentation: dumping layer tree");
ScriptDisallowedScope scriptDisallowedScope;
CheckedRef layer = *renderer.layer();
writeLayers(ts, layer, layer, layer->rect(), behavior);
writeSelection(ts, renderer);
return ts.release();
}
String externalRepresentation(LocalFrame* frame, OptionSet<RenderAsTextFlag> behavior)
{
ASSERT(frame);
ASSERT(frame->document());
if (!(behavior.contains(RenderAsTextFlag::DontUpdateLayout)) && frame->view())
frame->view()->updateLayoutAndStyleIfNeededRecursive({ LayoutOptions::IgnorePendingStylesheets, LayoutOptions::UpdateCompositingLayers });
auto* renderer = frame->contentRenderer();
if (!renderer)
return String();
Ref printContext = PrintContext::create(frame);
if (behavior.contains(RenderAsTextFlag::PrintingMode))
printContext->begin(renderer->width());
return externalRepresentation(*renderer, behavior);
}
void externalRepresentationForLocalFrame(TextStream &ts, LocalFrame& frame, OptionSet<RenderAsTextFlag> behavior)
{
ASSERT(frame.document());
if (RenderView* root = frame.contentRenderer()) {
if (RenderLayer* layer = root->layer())
writeLayers(ts, *layer, *layer, layer->rect(), behavior);
}
}
String externalRepresentation(Element* element, OptionSet<RenderAsTextFlag> behavior)
{
ASSERT(element);
// This function doesn't support printing mode.
ASSERT(!(behavior.contains(RenderAsTextFlag::PrintingMode)));
if (!(behavior.contains(RenderAsTextFlag::DontUpdateLayout)) && element->document().view())
element->document().view()->updateLayoutAndStyleIfNeededRecursive({ LayoutOptions::IgnorePendingStylesheets, LayoutOptions::UpdateCompositingLayers });
auto* renderer = element->renderer();
if (!is<RenderBox>(renderer))
return String();
return externalRepresentation(downcast<RenderBox>(*renderer), behavior | RenderAsTextFlag::ShowAllLayers);
}
static void writeCounterValuesFromChildren(TextStream& stream, const RenderElement* parent, bool& isFirstCounter)
{
if (!parent)
return;
for (auto& counter : childrenOfType<RenderCounter>(*parent)) {
if (!isFirstCounter)
stream << " ";
isFirstCounter = false;
String str(counter.text());
stream << str;
}
}
String counterValueForElement(Element* element)
{
// Make sure the element is not freed during the layout.
RefPtr<Element> elementRef(element);
element->document().updateLayout();
auto stream = createTextStream(element->document());
bool isFirstCounter = true;
// The counter renderers should be children of :before or :after pseudo-elements.
if (RefPtr before = element->beforePseudoElement())
writeCounterValuesFromChildren(stream, before->renderer(), isFirstCounter);
if (RefPtr after = element->afterPseudoElement())
writeCounterValuesFromChildren(stream, after->renderer(), isFirstCounter);
return stream.release();
}
String markerTextForListItem(Element* element)
{
// Make sure the element is not freed during the layout.
RefPtr protectedElement { element };
element->document().updateLayout();
auto* renderer = dynamicDowncast<RenderListItem>(element->renderer());
if (!renderer)
return String();
return renderer->markerTextWithoutSuffix();
}
} // namespace WebCore