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