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