1/* 2 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "RenderTreeAsText.h" 28 29#include "Document.h" 30#include "FlowThreadController.h" 31#include "Frame.h" 32#include "FrameSelection.h" 33#include "FrameView.h" 34#include "HTMLElement.h" 35#include "HTMLNames.h" 36#include "InlineTextBox.h" 37#include "PrintContext.h" 38#include "PseudoElement.h" 39#include "RenderBlockFlow.h" 40#include "RenderCounter.h" 41#include "RenderDetailsMarker.h" 42#include "RenderFileUploadControl.h" 43#include "RenderInline.h" 44#include "RenderIterator.h" 45#include "RenderLayer.h" 46#include "RenderLayerBacking.h" 47#include "RenderLineBreak.h" 48#include "RenderListItem.h" 49#include "RenderListMarker.h" 50#include "RenderNamedFlowFragment.h" 51#include "RenderNamedFlowThread.h" 52#include "RenderRegion.h" 53#include "RenderSVGContainer.h" 54#include "RenderSVGGradientStop.h" 55#include "RenderSVGImage.h" 56#include "RenderSVGInlineText.h" 57#include "RenderSVGPath.h" 58#include "RenderSVGResourceContainer.h" 59#include "RenderSVGRoot.h" 60#include "RenderSVGText.h" 61#include "RenderTableCell.h" 62#include "RenderView.h" 63#include "RenderWidget.h" 64#include "SVGRenderTreeAsText.h" 65#include "ShadowRoot.h" 66#include "SimpleLineLayoutResolver.h" 67#include "StyleProperties.h" 68#include <wtf/HexNumber.h> 69#include <wtf/Vector.h> 70#include <wtf/unicode/CharacterNames.h> 71 72namespace WebCore { 73 74using namespace HTMLNames; 75 76static void writeLayers(TextStream&, const RenderLayer* rootLayer, RenderLayer*, const LayoutRect& paintDirtyRect, int indent = 0, RenderAsTextBehavior = RenderAsTextBehaviorNormal); 77 78static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle) 79{ 80 switch (borderStyle) { 81 case BNONE: 82 ts << "none"; 83 break; 84 case BHIDDEN: 85 ts << "hidden"; 86 break; 87 case INSET: 88 ts << "inset"; 89 break; 90 case GROOVE: 91 ts << "groove"; 92 break; 93 case RIDGE: 94 ts << "ridge"; 95 break; 96 case OUTSET: 97 ts << "outset"; 98 break; 99 case DOTTED: 100 ts << "dotted"; 101 break; 102 case DASHED: 103 ts << "dashed"; 104 break; 105 case SOLID: 106 ts << "solid"; 107 break; 108 case DOUBLE: 109 ts << "double"; 110 break; 111 } 112 113 ts << " "; 114} 115 116static String getTagName(Node* n) 117{ 118 if (n->isDocumentNode()) 119 return ""; 120 if (n->nodeType() == Node::COMMENT_NODE) 121 return "COMMENT"; 122 return n->nodeName(); 123} 124 125static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node) 126{ 127 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag)) 128 return false; 129 130 const HTMLElement* elem = toHTMLElement(node); 131 if (elem->getAttribute(classAttr) != "Apple-style-span") 132 return false; 133 134 if (!node->hasChildNodes()) 135 return true; 136 137 const StyleProperties* inlineStyleDecl = elem->inlineStyle(); 138 return (!inlineStyleDecl || inlineStyleDecl->isEmpty()); 139} 140 141String quoteAndEscapeNonPrintables(const String& s) 142{ 143 StringBuilder result; 144 result.append('"'); 145 for (unsigned i = 0; i != s.length(); ++i) { 146 UChar c = s[i]; 147 if (c == '\\') { 148 result.append('\\'); 149 result.append('\\'); 150 } else if (c == '"') { 151 result.append('\\'); 152 result.append('"'); 153 } else if (c == '\n' || c == noBreakSpace) 154 result.append(' '); 155 else { 156 if (c >= 0x20 && c < 0x7F) 157 result.append(c); 158 else { 159 result.append('\\'); 160 result.append('x'); 161 result.append('{'); 162 appendUnsignedAsHex(c, result); 163 result.append('}'); 164 } 165 } 166 } 167 result.append('"'); 168 return result.toString(); 169} 170 171void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior) 172{ 173 ts << o.renderName(); 174 175 if (behavior & RenderAsTextShowAddresses) 176 ts << " " << static_cast<const void*>(&o); 177 178 if (o.style().zIndex()) 179 ts << " zI: " << o.style().zIndex(); 180 181 if (o.node()) { 182 String tagName = getTagName(o.node()); 183 // FIXME: Temporary hack to make tests pass by simulating the old generated content output. 184 if (o.isPseudoElement() || (o.parent() && o.parent()->isPseudoElement())) 185 tagName = emptyAtom; 186 if (!tagName.isEmpty()) { 187 ts << " {" << tagName << "}"; 188 // flag empty or unstyled AppleStyleSpan because we never 189 // want to leave them in the DOM 190 if (isEmptyOrUnstyledAppleStyleSpan(o.node())) 191 ts << " *empty or unstyled AppleStyleSpan*"; 192 } 193 } 194 195 RenderBlock* cb = o.containingBlock(); 196 bool adjustForTableCells = cb ? cb->isTableCell() : false; 197 198 LayoutRect r; 199 if (o.isText()) { 200 // 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 201 // many test results. 202 const RenderText& text = toRenderText(o); 203 r = IntRect(text.firstRunLocation(), text.linesBoundingBox().size()); 204 if (!text.firstTextBox() && !text.simpleLineLayout()) 205 adjustForTableCells = false; 206 } else if (o.isBR()) { 207 const RenderLineBreak& br = toRenderLineBreak(o); 208 IntRect linesBox = br.linesBoundingBox(); 209 r = IntRect(linesBox.x(), linesBox.y(), linesBox.width(), linesBox.height()); 210 if (!br.inlineBoxWrapper()) 211 adjustForTableCells = false; 212 } else if (o.isRenderInline()) { 213 const RenderInline& inlineFlow = toRenderInline(o); 214 // FIXME: Would be better not to just dump 0, 0 as the x and y here. 215 r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height()); 216 adjustForTableCells = false; 217 } else if (o.isTableCell()) { 218 // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like 219 // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are 220 // captured by the results. 221 const RenderTableCell& cell = *toRenderTableCell(&o); 222 r = LayoutRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter()); 223 } else if (o.isBox()) 224 r = toRenderBox(o).frameRect(); 225 226 // FIXME: Temporary in order to ensure compatibility with existing layout test results. 227 if (adjustForTableCells) 228 r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore()); 229 230 // FIXME: Convert layout test results to report sub-pixel values, in the meantime using enclosingIntRect 231 // for consistency with old results. 232 ts << " " << enclosingIntRect(r); 233 234 if (!o.isText()) { 235 if (o.isFileUploadControl()) 236 ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue()); 237 238 if (o.parent()) { 239 Color color = o.style().visitedDependentColor(CSSPropertyColor); 240 if (o.parent()->style().visitedDependentColor(CSSPropertyColor) != color) 241 ts << " [color=" << color.nameForRenderTreeAsText() << "]"; 242 243 // Do not dump invalid or transparent backgrounds, since that is the default. 244 Color backgroundColor = o.style().visitedDependentColor(CSSPropertyBackgroundColor); 245 if (o.parent()->style().visitedDependentColor(CSSPropertyBackgroundColor) != backgroundColor 246 && backgroundColor.isValid() && backgroundColor.rgb()) 247 ts << " [bgcolor=" << backgroundColor.nameForRenderTreeAsText() << "]"; 248 249 Color textFillColor = o.style().visitedDependentColor(CSSPropertyWebkitTextFillColor); 250 if (o.parent()->style().visitedDependentColor(CSSPropertyWebkitTextFillColor) != textFillColor 251 && textFillColor.isValid() && textFillColor != color && textFillColor.rgb()) 252 ts << " [textFillColor=" << textFillColor.nameForRenderTreeAsText() << "]"; 253 254 Color textStrokeColor = o.style().visitedDependentColor(CSSPropertyWebkitTextStrokeColor); 255 if (o.parent()->style().visitedDependentColor(CSSPropertyWebkitTextStrokeColor) != textStrokeColor 256 && textStrokeColor.isValid() && textStrokeColor != color && textStrokeColor.rgb()) 257 ts << " [textStrokeColor=" << textStrokeColor.nameForRenderTreeAsText() << "]"; 258 259 if (o.parent()->style().textStrokeWidth() != o.style().textStrokeWidth() && o.style().textStrokeWidth() > 0) 260 ts << " [textStrokeWidth=" << o.style().textStrokeWidth() << "]"; 261 } 262 263 if (!o.isBoxModelObject() || o.isLineBreak()) 264 return; 265 266 const RenderBoxModelObject& box = toRenderBoxModelObject(o); 267 if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) { 268 ts << " [border:"; 269 270 BorderValue prevBorder = o.style().borderTop(); 271 if (!box.borderTop()) 272 ts << " none"; 273 else { 274 ts << " (" << box.borderTop() << "px "; 275 printBorderStyle(ts, o.style().borderTopStyle()); 276 Color col = o.style().borderTopColor(); 277 if (!col.isValid()) 278 col = o.style().color(); 279 ts << col.nameForRenderTreeAsText() << ")"; 280 } 281 282 if (o.style().borderRight() != prevBorder) { 283 prevBorder = o.style().borderRight(); 284 if (!box.borderRight()) 285 ts << " none"; 286 else { 287 ts << " (" << box.borderRight() << "px "; 288 printBorderStyle(ts, o.style().borderRightStyle()); 289 Color col = o.style().borderRightColor(); 290 if (!col.isValid()) 291 col = o.style().color(); 292 ts << col.nameForRenderTreeAsText() << ")"; 293 } 294 } 295 296 if (o.style().borderBottom() != prevBorder) { 297 prevBorder = box.style().borderBottom(); 298 if (!box.borderBottom()) 299 ts << " none"; 300 else { 301 ts << " (" << box.borderBottom() << "px "; 302 printBorderStyle(ts, o.style().borderBottomStyle()); 303 Color col = o.style().borderBottomColor(); 304 if (!col.isValid()) 305 col = o.style().color(); 306 ts << col.nameForRenderTreeAsText() << ")"; 307 } 308 } 309 310 if (o.style().borderLeft() != prevBorder) { 311 prevBorder = o.style().borderLeft(); 312 if (!box.borderLeft()) 313 ts << " none"; 314 else { 315 ts << " (" << box.borderLeft() << "px "; 316 printBorderStyle(ts, o.style().borderLeftStyle()); 317 Color col = o.style().borderLeftColor(); 318 if (!col.isValid()) 319 col = o.style().color(); 320 ts << col.nameForRenderTreeAsText() << ")"; 321 } 322 } 323 324 ts << "]"; 325 } 326 327#if ENABLE(MATHML) 328 // We want to show any layout padding, both CSS padding and intrinsic padding, so we can't just check o.style().hasPadding(). 329 if (o.isRenderMathMLBlock() && (box.paddingTop() || box.paddingRight() || box.paddingBottom() || box.paddingLeft())) { 330 ts << " ["; 331 LayoutUnit cssTop = box.computedCSSPaddingTop(); 332 LayoutUnit cssRight = box.computedCSSPaddingRight(); 333 LayoutUnit cssBottom = box.computedCSSPaddingBottom(); 334 LayoutUnit cssLeft = box.computedCSSPaddingLeft(); 335 if (box.paddingTop() != cssTop || box.paddingRight() != cssRight || box.paddingBottom() != cssBottom || box.paddingLeft() != cssLeft) { 336 ts << "intrinsic "; 337 if (cssTop || cssRight || cssBottom || cssLeft) 338 ts << "+ CSS "; 339 } 340 ts << "padding: " << roundToInt(box.paddingTop()) << " " << roundToInt(box.paddingRight()) << " " << roundToInt(box.paddingBottom()) << " " << roundToInt(box.paddingLeft()) << "]"; 341 } 342#endif 343 } 344 345 if (o.isTableCell()) { 346 const RenderTableCell& c = *toRenderTableCell(&o); 347 ts << " [r=" << c.rowIndex() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]"; 348 } 349 350#if ENABLE(DETAILS_ELEMENT) 351 if (o.isDetailsMarker()) { 352 ts << ": "; 353 switch (toRenderDetailsMarker(o).orientation()) { 354 case RenderDetailsMarker::Left: 355 ts << "left"; 356 break; 357 case RenderDetailsMarker::Right: 358 ts << "right"; 359 break; 360 case RenderDetailsMarker::Up: 361 ts << "up"; 362 break; 363 case RenderDetailsMarker::Down: 364 ts << "down"; 365 break; 366 } 367 } 368#endif 369 370 if (o.isListMarker()) { 371 String text = toRenderListMarker(o).text(); 372 if (!text.isEmpty()) { 373 if (text.length() != 1) 374 text = quoteAndEscapeNonPrintables(text); 375 else { 376 switch (text[0]) { 377 case bullet: 378 text = "bullet"; 379 break; 380 case blackSquare: 381 text = "black square"; 382 break; 383 case whiteBullet: 384 text = "white bullet"; 385 break; 386 default: 387 text = quoteAndEscapeNonPrintables(text); 388 } 389 } 390 ts << ": " << text; 391 } 392 } 393 394 if (behavior & RenderAsTextShowIDAndClass) { 395 if (Element* element = o.node() && o.node()->isElementNode() ? toElement(o.node()) : 0) { 396 if (element->hasID()) 397 ts << " id=\"" + element->getIdAttribute() + "\""; 398 399 if (element->hasClass()) { 400 ts << " class=\""; 401 for (size_t i = 0; i < element->classNames().size(); ++i) { 402 if (i > 0) 403 ts << " "; 404 ts << element->classNames()[i]; 405 } 406 ts << "\""; 407 } 408 } 409 } 410 411 if (behavior & RenderAsTextShowLayoutState) { 412 bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout(); 413 if (needsLayout) 414 ts << " (needs layout:"; 415 416 bool havePrevious = false; 417 if (o.selfNeedsLayout()) { 418 ts << " self"; 419 havePrevious = true; 420 } 421 422 if (o.needsPositionedMovementLayout()) { 423 if (havePrevious) 424 ts << ","; 425 havePrevious = true; 426 ts << " positioned movement"; 427 } 428 429 if (o.normalChildNeedsLayout()) { 430 if (havePrevious) 431 ts << ","; 432 havePrevious = true; 433 ts << " child"; 434 } 435 436 if (o.posChildNeedsLayout()) { 437 if (havePrevious) 438 ts << ","; 439 ts << " positioned child"; 440 } 441 442 if (needsLayout) 443 ts << ")"; 444 } 445 446 if (behavior & RenderAsTextShowOverflow && o.isBox()) { 447 const RenderBox& box = toRenderBox(o); 448 if (box.hasRenderOverflow()) { 449 LayoutRect layoutOverflow = box.layoutOverflowRect(); 450 ts << " (layout overflow " << layoutOverflow.x().toInt() << "," << layoutOverflow.y().toInt() << " " << layoutOverflow.width().toInt() << "x" << layoutOverflow.height().toInt() << ")"; 451 452 if (box.hasVisualOverflow()) { 453 LayoutRect visualOverflow = box.visualOverflowRect(); 454 ts << " (visual overflow " << visualOverflow.x().toInt() << "," << visualOverflow.y().toInt() << " " << visualOverflow.width().toInt() << "x" << visualOverflow.height().toInt() << ")"; 455 } 456 } 457 } 458} 459 460static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) 461{ 462 // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder 463 // to detect any changes caused by the conversion to floating point. :( 464 int x = run.x(); 465 int y = run.y(); 466 int logicalWidth = ceilf(run.left() + run.logicalWidth()) - x; 467 468 // FIXME: Table cell adjustment is temporary until results can be updated. 469 if (o.containingBlock()->isTableCell()) 470 y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore(); 471 472 ts << "text run at (" << x << "," << y << ") width " << logicalWidth; 473 if (!run.isLeftToRightDirection() || run.dirOverride()) { 474 ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR"); 475 if (run.dirOverride()) 476 ts << " override"; 477 } 478 ts << ": " 479 << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len())); 480 if (run.hasHyphen()) 481 ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style().hyphenString()); 482 ts << "\n"; 483} 484 485static void writeSimpleLine(TextStream& ts, const RenderText& o, const LayoutRect& rect, const String& text) 486{ 487 int x = rect.x(); 488 int y = rect.y(); 489 int logicalWidth = ceilf(rect.x() + rect.width()) - x; 490 491 if (o.containingBlock()->isTableCell()) 492 y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore(); 493 494 ts << "text run at (" << x << "," << y << ") width " << logicalWidth; 495 ts << ": " 496 << quoteAndEscapeNonPrintables(text); 497 ts << "\n"; 498} 499 500void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior) 501{ 502 if (o.isSVGShape()) { 503 write(ts, *toRenderSVGShape(&o), indent); 504 return; 505 } 506 if (o.isSVGGradientStop()) { 507 writeSVGGradientStop(ts, *toRenderSVGGradientStop(&o), indent); 508 return; 509 } 510 if (o.isSVGResourceContainer()) { 511 writeSVGResourceContainer(ts, toRenderSVGResourceContainer(o), indent); 512 return; 513 } 514 if (o.isSVGContainer()) { 515 writeSVGContainer(ts, toRenderSVGContainer(o), indent); 516 return; 517 } 518 if (o.isSVGRoot()) { 519 write(ts, *toRenderSVGRoot(&o), indent); 520 return; 521 } 522 if (o.isSVGText()) { 523 writeSVGText(ts, *toRenderSVGText(&o), indent); 524 return; 525 } 526 if (o.isSVGInlineText()) { 527 writeSVGInlineText(ts, toRenderSVGInlineText(o), indent); 528 return; 529 } 530 if (o.isSVGImage()) { 531 writeSVGImage(ts, *toRenderSVGImage(&o), indent); 532 return; 533 } 534 535 writeIndent(ts, indent); 536 537 RenderTreeAsText::writeRenderObject(ts, o, behavior); 538 ts << "\n"; 539 540 if (o.isText()) { 541 auto& text = toRenderText(o); 542 if (auto layout = text.simpleLineLayout()) { 543 ASSERT(!text.firstTextBox()); 544 auto resolver = runResolver(toRenderBlockFlow(*text.parent()), *layout); 545 for (auto it = resolver.begin(), end = resolver.end(); it != end; ++it) { 546 auto run = *it; 547 writeIndent(ts, indent + 1); 548 writeSimpleLine(ts, text, run.rect(), run.text()); 549 } 550 } else { 551 for (auto box = text.firstTextBox(); box; box = box->nextTextBox()) { 552 writeIndent(ts, indent + 1); 553 writeTextRun(ts, text, *box); 554 } 555 } 556 557 } else { 558 for (RenderObject* child = toRenderElement(o).firstChild(); child; child = child->nextSibling()) { 559 if (child->hasLayer()) 560 continue; 561 write(ts, *child, indent + 1, behavior); 562 } 563 } 564 565 if (o.isWidget()) { 566 Widget* widget = toRenderWidget(&o)->widget(); 567 if (widget && widget->isFrameView()) { 568 FrameView* view = toFrameView(widget); 569 if (RenderView* root = view->frame().contentRenderer()) { 570 if (!(behavior & RenderAsTextDontUpdateLayout)) 571 view->layout(); 572 RenderLayer* l = root->layer(); 573 if (l) 574 writeLayers(ts, l, l, l->rect(), indent + 1, behavior); 575 } 576 } 577 } 578} 579 580enum LayerPaintPhase { 581 LayerPaintPhaseAll = 0, 582 LayerPaintPhaseBackground = -1, 583 LayerPaintPhaseForeground = 1 584}; 585 586static void write(TextStream& ts, RenderLayer& l, 587 const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, const LayoutRect& clipRect, const LayoutRect& outlineClipRect, 588 LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal) 589{ 590 IntRect adjustedLayoutBounds = pixelSnappedIntRect(layerBounds); 591 IntRect adjustedBackgroundClipRect = pixelSnappedIntRect(backgroundClipRect); 592 IntRect adjustedClipRect = pixelSnappedIntRect(clipRect); 593 IntRect adjustedOutlineClipRect = pixelSnappedIntRect(outlineClipRect); 594 595 writeIndent(ts, indent); 596 597 ts << "layer "; 598 599 if (behavior & RenderAsTextShowAddresses) 600 ts << static_cast<const void*>(&l) << " "; 601 602 ts << adjustedLayoutBounds; 603 604 if (!adjustedLayoutBounds.isEmpty()) { 605 if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds)) 606 ts << " backgroundClip " << adjustedBackgroundClipRect; 607 if (!adjustedClipRect.contains(adjustedLayoutBounds)) 608 ts << " clip " << adjustedClipRect; 609 if (!adjustedOutlineClipRect.contains(adjustedLayoutBounds)) 610 ts << " outlineClip " << adjustedOutlineClipRect; 611 } 612 613 if (l.renderer().hasOverflowClip()) { 614 if (l.scrollXOffset()) 615 ts << " scrollX " << l.scrollXOffset(); 616 if (l.scrollYOffset()) 617 ts << " scrollY " << l.scrollYOffset(); 618 if (l.renderBox() && l.renderBox()->pixelSnappedClientWidth() != l.scrollWidth()) 619 ts << " scrollWidth " << l.scrollWidth(); 620 if (l.renderBox() && l.renderBox()->pixelSnappedClientHeight() != l.scrollHeight()) 621 ts << " scrollHeight " << l.scrollHeight(); 622 } 623 624 if (paintPhase == LayerPaintPhaseBackground) 625 ts << " layerType: background only"; 626 else if (paintPhase == LayerPaintPhaseForeground) 627 ts << " layerType: foreground only"; 628 629 if (behavior & RenderAsTextShowCompositedLayers) { 630 if (l.isComposited()) 631 ts << " (composited, bounds=" << l.backing()->compositedBounds() << ", drawsContent=" << l.backing()->graphicsLayer()->drawsContent() << ", paints into ancestor=" << l.backing()->paintsIntoCompositedAncestor() << ")"; 632 } 633 634#if ENABLE(CSS_COMPOSITING) 635 if (l.isolatesBlending()) 636 ts << " isolatesBlending"; 637 if (l.hasBlendMode()) 638 ts << " blendMode: " << compositeOperatorName(CompositeSourceOver, l.blendMode()); 639#endif 640 641 ts << "\n"; 642 643 if (paintPhase != LayerPaintPhaseBackground) 644 write(ts, l.renderer(), indent + 1, behavior); 645} 646 647static void writeRenderRegionList(const RenderRegionList& flowThreadRegionList, TextStream& ts, int indent) 648{ 649 for (const auto& renderRegion : flowThreadRegionList) { 650 writeIndent(ts, indent); 651 ts << static_cast<const RenderObject*>(renderRegion)->renderName(); 652 653 Element* generatingElement = renderRegion->generatingElement(); 654 if (generatingElement) { 655 bool isRenderNamedFlowFragment = renderRegion->isRenderNamedFlowFragment(); 656 if (isRenderNamedFlowFragment && toRenderNamedFlowFragment(renderRegion)->hasCustomRegionStyle()) 657 ts << " region style: 1"; 658 if (renderRegion->hasAutoLogicalHeight()) 659 ts << " hasAutoLogicalHeight"; 660 661 if (isRenderNamedFlowFragment) 662 ts << " (anonymous child of"; 663 664 StringBuilder tagName; 665 tagName.append(generatingElement->nodeName()); 666 667 RenderElement* renderElementForRegion = isRenderNamedFlowFragment ? renderRegion->parent() : renderRegion; 668 if (renderElementForRegion->isPseudoElement()) { 669 if (renderElementForRegion->element()->isBeforePseudoElement()) 670 tagName.append("::before"); 671 else if (renderElementForRegion->element()->isAfterPseudoElement()) 672 tagName.append("::after"); 673 } 674 675 ts << " {" << tagName.toString() << "}"; 676 677 if (generatingElement->hasID()) 678 ts << " #" << generatingElement->idForStyleResolution(); 679 680 if (isRenderNamedFlowFragment) 681 ts << ")"; 682 } 683 684 ts << "\n"; 685 } 686} 687 688static void writeRenderNamedFlowThreads(TextStream& ts, RenderView& renderView, const RenderLayer* rootLayer, 689 const LayoutRect& paintRect, int indent, RenderAsTextBehavior behavior) 690{ 691 if (!renderView.hasRenderNamedFlowThreads()) 692 return; 693 694 const RenderNamedFlowThreadList* list = renderView.flowThreadController().renderNamedFlowThreadList(); 695 696 writeIndent(ts, indent); 697 ts << "Named flows\n"; 698 699 for (RenderNamedFlowThreadList::const_iterator iter = list->begin(); iter != list->end(); ++iter) { 700 const RenderNamedFlowThread* renderFlowThread = *iter; 701 702 writeIndent(ts, indent + 1); 703 ts << "Named flow '" << renderFlowThread->flowThreadName() << "'\n"; 704 705 RenderLayer* layer = renderFlowThread->layer(); 706 writeLayers(ts, rootLayer, layer, paintRect, indent + 2, behavior); 707 708 // Display the valid and invalid render regions attached to this flow thread. 709 const RenderRegionList& validRegionsList = renderFlowThread->renderRegionList(); 710 const RenderRegionList& invalidRegionsList = renderFlowThread->invalidRenderRegionList(); 711 if (!validRegionsList.isEmpty()) { 712 writeIndent(ts, indent + 2); 713 ts << "Regions for named flow '" << renderFlowThread->flowThreadName() << "'\n"; 714 writeRenderRegionList(validRegionsList, ts, indent + 3); 715 } 716 if (!invalidRegionsList.isEmpty()) { 717 writeIndent(ts, indent + 2); 718 ts << "Invalid regions for named flow '" << renderFlowThread->flowThreadName() << "'\n"; 719 writeRenderRegionList(invalidRegionsList, ts, indent + 3); 720 } 721 } 722} 723 724static LayoutSize maxLayoutOverflow(const RenderBox* box) 725{ 726 LayoutRect overflowRect = box->layoutOverflowRect(); 727 return LayoutSize(overflowRect.maxX(), overflowRect.maxY()); 728} 729 730static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l, 731 const LayoutRect& paintRect, int indent, RenderAsTextBehavior behavior) 732{ 733 // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh. 734 LayoutRect paintDirtyRect(paintRect); 735 if (rootLayer == l) { 736 paintDirtyRect.setWidth(std::max<LayoutUnit>(paintDirtyRect.width(), rootLayer->renderBox()->layoutOverflowRect().maxX())); 737 paintDirtyRect.setHeight(std::max<LayoutUnit>(paintDirtyRect.height(), rootLayer->renderBox()->layoutOverflowRect().maxY())); 738 l->setSize(l->size().expandedTo(pixelSnappedIntSize(maxLayoutOverflow(l->renderBox()), LayoutPoint(0, 0)))); 739 } 740 741 // Calculate the clip rects we should use. 742 LayoutRect layerBounds; 743 ClipRect damageRect, clipRectToApply, outlineRect; 744 l->calculateRects(RenderLayer::ClipRectsContext(rootLayer, TemporaryClipRects), paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, l->offsetFromAncestor(rootLayer)); 745 746 // Ensure our lists are up-to-date. 747 l->updateLayerListsIfNeeded(); 748 749 bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : l->intersectsDamageRect(layerBounds, damageRect.rect(), rootLayer, l->offsetFromAncestor(rootLayer)); 750 Vector<RenderLayer*>* negList = l->negZOrderList(); 751 bool paintsBackgroundSeparately = negList && negList->size() > 0; 752 if (shouldPaint && paintsBackgroundSeparately) 753 write(ts, *l, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), LayerPaintPhaseBackground, indent, behavior); 754 755 if (negList) { 756 int currIndent = indent; 757 if (behavior & RenderAsTextShowLayerNesting) { 758 writeIndent(ts, indent); 759 ts << " negative z-order list(" << negList->size() << ")\n"; 760 ++currIndent; 761 } 762 for (unsigned i = 0; i != negList->size(); ++i) 763 writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, currIndent, behavior); 764 } 765 766 if (shouldPaint) 767 write(ts, *l, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior); 768 769 if (Vector<RenderLayer*>* normalFlowList = l->normalFlowList()) { 770 int currIndent = indent; 771 if (behavior & RenderAsTextShowLayerNesting) { 772 writeIndent(ts, indent); 773 ts << " normal flow list(" << normalFlowList->size() << ")\n"; 774 ++currIndent; 775 } 776 for (unsigned i = 0; i != normalFlowList->size(); ++i) 777 writeLayers(ts, rootLayer, normalFlowList->at(i), paintDirtyRect, currIndent, behavior); 778 } 779 780 if (Vector<RenderLayer*>* posList = l->posZOrderList()) { 781 size_t layerCount = 0; 782 for (unsigned i = 0; i != posList->size(); ++i) 783 if (!posList->at(i)->isFlowThreadCollectingGraphicsLayersUnderRegions()) 784 ++layerCount; 785 if (layerCount) { 786 int currIndent = indent; 787 // We only print the header if there's at list a non-RenderNamedFlowThread part of the list. 788 if (!posList->size() || !posList->at(0)->isFlowThreadCollectingGraphicsLayersUnderRegions()) { 789 if (behavior & RenderAsTextShowLayerNesting) { 790 writeIndent(ts, indent); 791 ts << " positive z-order list(" << posList->size() << ")\n"; 792 ++currIndent; 793 } 794 for (unsigned i = 0; i != posList->size(); ++i) { 795 // Do not print named flows twice. 796 if (!posList->at(i)->isFlowThreadCollectingGraphicsLayersUnderRegions()) 797 writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, currIndent, behavior); 798 } 799 } 800 } 801 } 802 803 // Altough the RenderFlowThread requires a layer, it is not collected by its parent, 804 // so we have to treat it as a special case. 805 if (l->renderer().isRenderView()) 806 writeRenderNamedFlowThreads(ts, toRenderView(l->renderer()), rootLayer, paintDirtyRect, indent, behavior); 807} 808 809static String nodePosition(Node* node) 810{ 811 StringBuilder result; 812 813 Element* body = node->document().body(); 814 Node* parent; 815 for (Node* n = node; n; n = parent) { 816 parent = n->parentOrShadowHostNode(); 817 if (n != node) 818 result.appendLiteral(" of "); 819 if (parent) { 820 if (body && n == body) { 821 // We don't care what offset body may be in the document. 822 result.appendLiteral("body"); 823 break; 824 } 825 if (n->isShadowRoot()) { 826 result.append('{'); 827 result.append(getTagName(n)); 828 result.append('}'); 829 } else { 830 result.appendLiteral("child "); 831 result.appendNumber(n->nodeIndex()); 832 result.appendLiteral(" {"); 833 result.append(getTagName(n)); 834 result.append('}'); 835 } 836 } else 837 result.appendLiteral("document"); 838 } 839 840 return result.toString(); 841} 842 843static void writeSelection(TextStream& ts, const RenderObject* renderer) 844{ 845 if (!renderer->isRenderView()) 846 return; 847 848 Frame* frame = renderer->document().frame(); 849 if (!frame) 850 return; 851 852 VisibleSelection selection = frame->selection().selection(); 853 if (selection.isCaret()) { 854 ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()); 855 if (selection.affinity() == UPSTREAM) 856 ts << " (upstream affinity)"; 857 ts << "\n"; 858 } else if (selection.isRange()) 859 ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()) << "\n" 860 << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().deprecatedNode()) << "\n"; 861} 862 863static String externalRepresentation(RenderBox* renderer, RenderAsTextBehavior behavior) 864{ 865 TextStream ts; 866 if (!renderer->hasLayer()) 867 return ts.release(); 868 869 RenderLayer* layer = renderer->layer(); 870 writeLayers(ts, layer, layer, layer->rect(), 0, behavior); 871 writeSelection(ts, renderer); 872 return ts.release(); 873} 874 875String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior) 876{ 877 RenderObject* renderer = frame->contentRenderer(); 878 if (!renderer || !renderer->isBox()) 879 return String(); 880 881 PrintContext printContext(frame); 882 if (behavior & RenderAsTextPrintingMode) 883 printContext.begin(toRenderBox(renderer)->width()); 884 if (!(behavior & RenderAsTextDontUpdateLayout)) 885 frame->document()->updateLayout(); 886 887 return externalRepresentation(toRenderBox(renderer), behavior); 888} 889 890String externalRepresentation(Element* element, RenderAsTextBehavior behavior) 891{ 892 RenderObject* renderer = element->renderer(); 893 if (!renderer || !renderer->isBox()) 894 return String(); 895 // Doesn't support printing mode. 896 ASSERT(!(behavior & RenderAsTextPrintingMode)); 897 if (!(behavior & RenderAsTextDontUpdateLayout)) 898 element->document().updateLayout(); 899 900 return externalRepresentation(toRenderBox(renderer), behavior | RenderAsTextShowAllLayers); 901} 902 903static void writeCounterValuesFromChildren(TextStream& stream, const RenderElement* parent, bool& isFirstCounter) 904{ 905 if (!parent) 906 return; 907 for (auto& counter : childrenOfType<RenderCounter>(*parent)) { 908 if (!isFirstCounter) 909 stream << " "; 910 isFirstCounter = false; 911 String str(counter.text()); 912 stream << str; 913 } 914} 915 916String counterValueForElement(Element* element) 917{ 918 // Make sure the element is not freed during the layout. 919 RefPtr<Element> elementRef(element); 920 element->document().updateLayout(); 921 TextStream stream; 922 bool isFirstCounter = true; 923 // The counter renderers should be children of :before or :after pseudo-elements. 924 if (PseudoElement* before = element->beforePseudoElement()) 925 writeCounterValuesFromChildren(stream, before->renderer(), isFirstCounter); 926 if (PseudoElement* after = element->afterPseudoElement()) 927 writeCounterValuesFromChildren(stream, after->renderer(), isFirstCounter); 928 return stream.release(); 929} 930 931String markerTextForListItem(Element* element) 932{ 933 // Make sure the element is not freed during the layout. 934 RefPtr<Element> elementRef(element); 935 element->document().updateLayout(); 936 937 RenderObject* renderer = element->renderer(); 938 if (!renderer || !renderer->isListItem()) 939 return String(); 940 941 return toRenderListItem(renderer)->markerText(); 942} 943 944} // namespace WebCore 945